Skip to content

Commit d91559b

Browse files
committed
RFC: extend unused_io_amount to cover async io.
Clippy helpfully warns about code like this, telling you that you probably meant "write_all": fn say_hi<W:Write>(w: &mut W) { w.write(b"hello").unwrap(); } This patch attempts to extend the lint so it also covers this case: async fn say_hi<W:AsyncWrite>(w: &mut W) { w.write(b"hello").await.unwrap(); } (I've run into this second case several times in my own programming, and so have my coworkers, so unless we're especially accident-prone in this area, it's probably worth addressing?) Since this is my first attempt at a clippy patch, I've probably made all kinds of mistakes: please help me fix them? I'd like to learn more here. Open questions I have: * Should this be a separate lint from unused_io_amount? Maybe unused_async_io_amount? If so, how should I structure their shared code? * Should this cover tokio's AsyncWrite too? * Is it okay to write lints for stuff that isn't part of the standard library? I see that "regex" also has lints, and I figure that "futures" is probably okay too, since it's an official rust-lang repository. * What other tests are needed? * How should I improve the code? Thanks for your time!
1 parent bb7b6be commit d91559b

File tree

6 files changed

+56
-18
lines changed

6 files changed

+56
-18
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ itertools = "0.10"
4747
quote = "1.0"
4848
serde = { version = "1.0", features = ["derive"] }
4949
syn = { version = "1.0", features = ["full"] }
50+
futures = "0.3"
5051
parking_lot = "0.11.2"
5152

5253
[build-dependencies]

clippy_lints/src/unused_io_amount.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,27 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
7070

7171
fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
7272
let mut call = call;
73-
while let hir::ExprKind::MethodCall(path, _, args, _) = call.kind {
74-
if matches!(&*path.ident.as_str(), "or" | "or_else" | "ok") {
75-
call = &args[0];
76-
} else {
77-
break;
73+
loop {
74+
match call.kind {
75+
hir::ExprKind::MethodCall(path, _, args, _) => {
76+
if matches!(&*path.ident.as_str(), "or" | "or_else" | "ok") {
77+
call = &args[0];
78+
} else {
79+
break;
80+
}
81+
},
82+
hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) => {
83+
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
84+
if matches!(
85+
func.kind,
86+
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
87+
) {
88+
call = arg_0;
89+
}
90+
}
91+
break;
92+
},
93+
_ => break,
7894
}
7995
}
8096
check_method_call(cx, call, expr);
@@ -83,8 +99,10 @@ fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<
8399
fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
84100
if let hir::ExprKind::MethodCall(path, _, _, _) = call.kind {
85101
let symbol = &*path.ident.as_str();
86-
let read_trait = match_trait_method(cx, call, &paths::IO_READ);
87-
let write_trait = match_trait_method(cx, call, &paths::IO_WRITE);
102+
let read_trait = match_trait_method(cx, call, &paths::IO_READ)
103+
|| match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT);
104+
let write_trait = match_trait_method(cx, call, &paths::IO_WRITE)
105+
|| match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT);
88106

89107
match (read_trait, write_trait, symbol) {
90108
(true, _, "read") => span_lint(

clippy_utils/src/paths.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "From
6464
pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
6565
pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
6666
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
67+
#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
68+
pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
69+
#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
70+
pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"];
6771
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
6872
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
6973
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];

tests/compile-test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
2121
static TEST_DEPENDENCIES: &[&str] = &[
2222
"clippy_utils",
2323
"derive_new",
24+
"futures",
2425
"if_chain",
2526
"itertools",
2627
"quote",
@@ -38,6 +39,8 @@ extern crate clippy_utils;
3839
#[allow(unused_extern_crates)]
3940
extern crate derive_new;
4041
#[allow(unused_extern_crates)]
42+
extern crate futures;
43+
#[allow(unused_extern_crates)]
4144
extern crate if_chain;
4245
#[allow(unused_extern_crates)]
4346
extern crate itertools;

tests/ui/unused_io_amount.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![allow(dead_code)]
22
#![warn(clippy::unused_io_amount)]
33

4+
extern crate futures;
5+
use futures::io::{AsyncWrite, AsyncWriteExt};
46
use std::io::{self, Read};
57

68
fn question_mark<T: io::Read + io::Write>(s: &mut T) -> io::Result<()> {
@@ -61,4 +63,8 @@ fn combine_or(file: &str) -> Result<(), Error> {
6163
Ok(())
6264
}
6365

66+
async fn bad_async_write<W: AsyncWrite + Unpin>(w: &mut W) {
67+
w.write(b"hello world").await.unwrap();
68+
}
69+
6470
fn main() {}

tests/ui/unused_io_amount.stderr

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,61 @@
11
error: written amount is not handled. Use `Write::write_all` instead
2-
--> $DIR/unused_io_amount.rs:7:5
2+
--> $DIR/unused_io_amount.rs:9:5
33
|
44
LL | s.write(b"test")?;
55
| ^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::unused-io-amount` implied by `-D warnings`
88

99
error: read amount is not handled. Use `Read::read_exact` instead
10-
--> $DIR/unused_io_amount.rs:9:5
10+
--> $DIR/unused_io_amount.rs:11:5
1111
|
1212
LL | s.read(&mut buf)?;
1313
| ^^^^^^^^^^^^^^^^^
1414

1515
error: written amount is not handled. Use `Write::write_all` instead
16-
--> $DIR/unused_io_amount.rs:14:5
16+
--> $DIR/unused_io_amount.rs:16:5
1717
|
1818
LL | s.write(b"test").unwrap();
1919
| ^^^^^^^^^^^^^^^^^^^^^^^^^
2020

2121
error: read amount is not handled. Use `Read::read_exact` instead
22-
--> $DIR/unused_io_amount.rs:16:5
22+
--> $DIR/unused_io_amount.rs:18:5
2323
|
2424
LL | s.read(&mut buf).unwrap();
2525
| ^^^^^^^^^^^^^^^^^^^^^^^^^
2626

2727
error: read amount is not handled
28-
--> $DIR/unused_io_amount.rs:20:5
28+
--> $DIR/unused_io_amount.rs:22:5
2929
|
3030
LL | s.read_vectored(&mut [io::IoSliceMut::new(&mut [])])?;
3131
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3232

3333
error: written amount is not handled
34-
--> $DIR/unused_io_amount.rs:21:5
34+
--> $DIR/unused_io_amount.rs:23:5
3535
|
3636
LL | s.write_vectored(&[io::IoSlice::new(&[])])?;
3737
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3838

3939
error: read amount is not handled. Use `Read::read_exact` instead
40-
--> $DIR/unused_io_amount.rs:28:5
40+
--> $DIR/unused_io_amount.rs:30:5
4141
|
4242
LL | reader.read(&mut result).ok()?;
4343
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4444

4545
error: read amount is not handled. Use `Read::read_exact` instead
46-
--> $DIR/unused_io_amount.rs:37:5
46+
--> $DIR/unused_io_amount.rs:39:5
4747
|
4848
LL | reader.read(&mut result).or_else(|err| Err(err))?;
4949
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5050

5151
error: read amount is not handled. Use `Read::read_exact` instead
52-
--> $DIR/unused_io_amount.rs:49:5
52+
--> $DIR/unused_io_amount.rs:51:5
5353
|
5454
LL | reader.read(&mut result).or(Err(Error::Kind))?;
5555
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5656

5757
error: read amount is not handled. Use `Read::read_exact` instead
58-
--> $DIR/unused_io_amount.rs:56:5
58+
--> $DIR/unused_io_amount.rs:58:5
5959
|
6060
LL | / reader
6161
LL | | .read(&mut result)
@@ -64,5 +64,11 @@ LL | | .or(Err(Error::Kind))
6464
LL | | .expect("error");
6565
| |________________________^
6666

67-
error: aborting due to 10 previous errors
67+
error: written amount is not handled. Use `Write::write_all` instead
68+
--> $DIR/unused_io_amount.rs:67:5
69+
|
70+
LL | w.write(b"hello world").await.unwrap();
71+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72+
73+
error: aborting due to 11 previous errors
6874

0 commit comments

Comments
 (0)