about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/unused_io_amount.rs292
-rw-r--r--tests/ui/unused_io_amount.rs87
-rw-r--r--tests/ui/unused_io_amount.stderr141
3 files changed, 400 insertions, 120 deletions
diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs
index 1de9adfcb96..adc66e15ff5 100644
--- a/clippy_lints/src/unused_io_amount.rs
+++ b/clippy_lints/src/unused_io_amount.rs
@@ -1,9 +1,10 @@
-use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
-use clippy_utils::{is_trait_method, is_try, match_trait_method, paths};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths};
+use hir::{ExprKind, PatKind};
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::declare_lint_pass;
-use rustc_span::sym;
+use rustc_span::{sym, Span};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -45,126 +46,219 @@ declare_clippy_lint! {
 
 declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
 
+#[derive(Copy, Clone)]
+enum IoOp {
+    AsyncWrite(bool),
+    AsyncRead(bool),
+    SyncRead(bool),
+    SyncWrite(bool),
+}
+
 impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
-    fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
-        let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = s.kind else {
-            return;
-        };
+    /// We perform the check on the block level.
+    /// If we want to catch match and if expressions that act as returns of the block
+    ///   we need to check them at `check_expr` or `check_block` as they are not stmts
+    ///   but we can't check them at `check_expr` because we need the broader context
+    ///   because we should do this only for the final expression of the block, and not for
+    ///   `StmtKind::Local` which binds values => the io amount is used.
+    ///
+    /// To check for unused io amount in stmts, we only consider `StmtKind::Semi`.
+    /// `StmtKind::Local` is not considered because it binds values => the io amount is used.
+    /// `StmtKind::Expr` is not considered because requires unit type => the io amount is used.
+    /// `StmtKind::Item` is not considered because it's not an expression.
+    ///
+    /// We then check the individual expressions via `check_expr`. We use the same logic for
+    /// semi expressions and the final expression as we need to check match and if expressions
+    /// for binding of the io amount to `Ok(_)`.
+    ///
+    /// We explicitly check for the match source to be Normal as it needs special logic
+    /// to consider the arms, and we want to avoid breaking the logic for situations where things
+    /// get desugared to match.
+    fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) {
+        for stmt in block.stmts {
+            if let hir::StmtKind::Semi(exp) = stmt.kind {
+                check_expr(cx, exp);
+            }
+        }
 
-        match expr.kind {
-            hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
-                if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
-                    if matches!(
-                        func.kind,
-                        hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
-                    ) {
-                        check_map_error(cx, arg_0, expr);
+        if let Some(exp) = block.expr
+            && matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _))
+        {
+            check_expr(cx, exp);
+        }
+    }
+}
+
+fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
+    match expr.kind {
+        hir::ExprKind::If(cond, _, _)
+            if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
+                && pattern_is_ignored_ok(cx, pat)
+                && let Some(op) = should_lint(cx, init) =>
+        {
+            emit_lint(cx, cond.span, op, &[pat.span]);
+        },
+        hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
+            let found_arms: Vec<_> = arms
+                .iter()
+                .filter_map(|arm| {
+                    if pattern_is_ignored_ok(cx, arm.pat) {
+                        Some(arm.span)
+                    } else {
+                        None
                     }
-                } else {
-                    check_map_error(cx, res, expr);
-                }
-            },
-            hir::ExprKind::MethodCall(path, arg_0, ..) => match path.ident.as_str() {
-                "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" | "is_ok" | "is_err" => {
-                    check_map_error(cx, arg_0, expr);
-                },
-                _ => (),
-            },
-            _ => (),
+                })
+                .collect();
+            if !found_arms.is_empty() {
+                emit_lint(cx, expr.span, op, found_arms.as_slice());
+            }
+        },
+        _ if let Some(op) = should_lint(cx, expr) => {
+            emit_lint(cx, expr.span, op, &[]);
+        },
+        _ => {},
+    };
+}
+
+fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option<IoOp> {
+    inner = unpack_match(inner);
+    inner = unpack_try(inner);
+    inner = unpack_call_chain(inner);
+    inner = unpack_await(inner);
+    // we type-check it to get whether it's a read/write or their vectorized forms
+    // and keep only the ones that are produce io amount
+    check_io_mode(cx, inner)
+}
+
+fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool {
+    // the if checks whether we are in a result Ok( ) pattern
+    // and the return checks whether it is unhandled
+
+    if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind
+        // we check against Result::Ok to avoid linting on Err(_) or something else.
+        && is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
+    {
+        return match (inner_pat, ddp.as_opt_usize()) {
+            // Ok(_) pattern
+            ([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true,
+            // Ok(..) pattern
+            ([], Some(0)) => true,
+            _ => false,
+        };
+    }
+    false
+}
+
+fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+    while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
+        if matches!(
+            path.ident.as_str(),
+            "unwrap" | "expect" | "unwrap_or" | "unwrap_or_else" | "ok" | "is_ok" | "is_err" | "or_else" | "or"
+        ) {
+            expr = receiver;
+        } else {
+            break;
         }
     }
+    expr
+}
+
+fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+    while let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind
+        && matches!(
+            func.kind,
+            hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
+        )
+    {
+        expr = arg_0;
+    }
+    expr
+}
+
+fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+    while let hir::ExprKind::Match(res, _, _) = expr.kind {
+        expr = res;
+    }
+    expr
 }
 
 /// If `expr` is an (e).await, return the inner expression "e" that's being
 /// waited on.  Otherwise return None.
-fn try_remove_await<'a>(expr: &'a hir::Expr<'a>) -> Option<&hir::Expr<'a>> {
+fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &hir::Expr<'a> {
     if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
         if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
             if matches!(
                 func.kind,
                 hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
             ) {
-                return Some(arg_0);
+                return arg_0;
             }
         }
     }
-
-    None
+    expr
 }
 
-fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
-    let mut call = call;
-    while let hir::ExprKind::MethodCall(path, receiver, ..) = call.kind {
-        if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
-            call = receiver;
-        } else {
-            break;
-        }
-    }
+/// Check whether the current expr is a function call for an IO operation
+fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
+    let hir::ExprKind::MethodCall(path, ..) = call.kind else {
+        return None;
+    };
 
-    if let Some(call) = try_remove_await(call) {
-        check_method_call(cx, call, expr, true);
-    } else {
-        check_method_call(cx, call, expr, false);
+    let vectorized = match path.ident.as_str() {
+        "write_vectored" | "read_vectored" => true,
+        "write" | "read" => false,
+        _ => {
+            return None;
+        },
+    };
+
+    match (
+        is_trait_method(cx, call, sym::IoRead),
+        is_trait_method(cx, call, sym::IoWrite),
+        match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
+            || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT),
+        match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
+            || match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT),
+    ) {
+        (true, _, _, _) => Some(IoOp::SyncRead(vectorized)),
+        (_, true, _, _) => Some(IoOp::SyncWrite(vectorized)),
+        (_, _, true, _) => Some(IoOp::AsyncRead(vectorized)),
+        (_, _, _, true) => Some(IoOp::AsyncWrite(vectorized)),
+        _ => None,
     }
 }
 
-fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>, is_await: bool) {
-    if let hir::ExprKind::MethodCall(path, ..) = call.kind {
-        let symbol = path.ident.as_str();
-        let read_trait = if is_await {
-            match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
-                || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT)
-        } else {
-            is_trait_method(cx, call, sym::IoRead)
-        };
-        let write_trait = if is_await {
-            match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT)
-                || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
-        } else {
-            is_trait_method(cx, call, sym::IoWrite)
-        };
+fn emit_lint(cx: &LateContext<'_>, span: Span, op: IoOp, wild_cards: &[Span]) {
+    let (msg, help) = match op {
+        IoOp::AsyncRead(false) => (
+            "read amount is not handled",
+            Some("use `AsyncReadExt::read_exact` instead, or handle partial reads"),
+        ),
+        IoOp::SyncRead(false) => (
+            "read amount is not handled",
+            Some("use `Read::read_exact` instead, or handle partial reads"),
+        ),
+        IoOp::SyncWrite(false) => (
+            "written amount is not handled",
+            Some("use `Write::write_all` instead, or handle partial writes"),
+        ),
+        IoOp::AsyncWrite(false) => (
+            "written amount is not handled",
+            Some("use `AsyncWriteExt::write_all` instead, or handle partial writes"),
+        ),
+        IoOp::SyncRead(true) | IoOp::AsyncRead(true) => ("read amount is not handled", None),
+        IoOp::SyncWrite(true) | IoOp::AsyncWrite(true) => ("written amount is not handled", None),
+    };
 
-        match (read_trait, write_trait, symbol, is_await) {
-            (true, _, "read", false) => span_lint_and_help(
-                cx,
-                UNUSED_IO_AMOUNT,
-                expr.span,
-                "read amount is not handled",
-                None,
-                "use `Read::read_exact` instead, or handle partial reads",
-            ),
-            (true, _, "read", true) => span_lint_and_help(
-                cx,
-                UNUSED_IO_AMOUNT,
-                expr.span,
-                "read amount is not handled",
-                None,
-                "use `AsyncReadExt::read_exact` instead, or handle partial reads",
-            ),
-            (true, _, "read_vectored", _) => {
-                span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled");
-            },
-            (_, true, "write", false) => span_lint_and_help(
-                cx,
-                UNUSED_IO_AMOUNT,
-                expr.span,
-                "written amount is not handled",
-                None,
-                "use `Write::write_all` instead, or handle partial writes",
-            ),
-            (_, true, "write", true) => span_lint_and_help(
-                cx,
-                UNUSED_IO_AMOUNT,
-                expr.span,
-                "written amount is not handled",
-                None,
-                "use `AsyncWriteExt::write_all` instead, or handle partial writes",
-            ),
-            (_, true, "write_vectored", _) => {
-                span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled");
-            },
-            _ => (),
+    span_lint_and_then(cx, UNUSED_IO_AMOUNT, span, msg, |diag| {
+        if let Some(help_str) = help {
+            diag.help(help_str);
         }
-    }
+        for span in wild_cards {
+            diag.span_note(
+                *span,
+                "the result is consumed here, but the amount of I/O bytes remains unhandled",
+            );
+        }
+    });
 }
diff --git a/tests/ui/unused_io_amount.rs b/tests/ui/unused_io_amount.rs
index 62aec6e9eaf..9974600dad5 100644
--- a/tests/ui/unused_io_amount.rs
+++ b/tests/ui/unused_io_amount.rs
@@ -1,4 +1,5 @@
 #![allow(dead_code, clippy::needless_pass_by_ref_mut)]
+#![allow(clippy::redundant_pattern_matching)]
 #![warn(clippy::unused_io_amount)]
 
 extern crate futures;
@@ -142,4 +143,90 @@ async fn undetected_bad_async_write<W: AsyncWrite + Unpin>(w: &mut W) {
     future.await.unwrap();
 }
 
+fn match_okay_underscore<T: io::Read + io::Write>(s: &mut T) {
+    match s.write(b"test") {
+        //~^ ERROR: written amount is not handled
+        Ok(_) => todo!(),
+        //~^ NOTE: the result is consumed here, but the amount of I/O bytes remains unhandled
+        Err(_) => todo!(),
+    };
+
+    let mut buf = [0u8; 4];
+    match s.read(&mut buf) {
+        //~^ ERROR: read amount is not handled
+        Ok(_) => todo!(),
+        //~^ NOTE: the result is consumed here, but the amount of I/O bytes remains unhandled
+        Err(_) => todo!(),
+    }
+}
+
+fn match_okay_underscore_read_expr<T: io::Read + io::Write>(s: &mut T) {
+    match s.read(&mut [0u8; 4]) {
+        //~^ ERROR: read amount is not handled
+        Ok(_) => todo!(),
+        //~^ NOTE: the result is consumed here, but the amount of I/O bytes remains unhandled
+        Err(_) => todo!(),
+    }
+}
+
+fn match_okay_underscore_write_expr<T: io::Read + io::Write>(s: &mut T) {
+    match s.write(b"test") {
+        //~^ ERROR: written amount is not handled
+        Ok(_) => todo!(),
+        //~^ NOTE: the result is consumed here, but the amount of I/O bytes remains unhandled
+        Err(_) => todo!(),
+    }
+}
+
+fn returned_value_should_not_lint<T: io::Read + io::Write>(s: &mut T) -> Result<usize, std::io::Error> {
+    s.write(b"test")
+}
+
+fn if_okay_underscore_read_expr<T: io::Read + io::Write>(s: &mut T) {
+    if let Ok(_) = s.read(&mut [0u8; 4]) {
+        //~^ ERROR: read amount is not handled
+        todo!()
+    }
+}
+
+fn if_okay_underscore_write_expr<T: io::Read + io::Write>(s: &mut T) {
+    if let Ok(_) = s.write(b"test") {
+        //~^ ERROR: written amount is not handled
+        todo!()
+    }
+}
+
+fn if_okay_dots_write_expr<T: io::Read + io::Write>(s: &mut T) {
+    if let Ok(..) = s.write(b"test") {
+        //~^ ERROR: written amount is not handled
+        todo!()
+    }
+}
+
+fn if_okay_underscore_write_expr_true_negative<T: io::Read + io::Write>(s: &mut T) {
+    if let Ok(bound) = s.write(b"test") {
+        todo!()
+    }
+}
+
+fn match_okay_underscore_true_neg<T: io::Read + io::Write>(s: &mut T) {
+    match s.write(b"test") {
+        Ok(bound) => todo!(),
+        Err(_) => todo!(),
+    };
+}
+
+fn true_negative<T: io::Read + io::Write>(s: &mut T) {
+    let mut buf = [0u8; 4];
+    let read_amount = s.read(&mut buf).unwrap();
+}
+
+fn on_return_should_not_raise<T: io::Read + io::Write>(s: &mut T) -> io::Result<usize> {
+    /// this is bad code because it goes around the problem of handling the read amount
+    /// by returning it, which makes it impossible to know this is a resonpose from the
+    /// correct account.
+    let mut buf = [0u8; 4];
+    s.read(&mut buf)
+}
+
 fn main() {}
diff --git a/tests/ui/unused_io_amount.stderr b/tests/ui/unused_io_amount.stderr
index f9aef596a1c..4af56d264bf 100644
--- a/tests/ui/unused_io_amount.stderr
+++ b/tests/ui/unused_io_amount.stderr
@@ -1,5 +1,5 @@
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:9:5
+  --> $DIR/unused_io_amount.rs:10:5
    |
 LL |     s.write(b"test")?;
    |     ^^^^^^^^^^^^^^^^^
@@ -9,7 +9,7 @@ LL |     s.write(b"test")?;
    = help: to override `-D warnings` add `#[allow(clippy::unused_io_amount)]`
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:12:5
+  --> $DIR/unused_io_amount.rs:13:5
    |
 LL |     s.read(&mut buf)?;
    |     ^^^^^^^^^^^^^^^^^
@@ -17,7 +17,7 @@ LL |     s.read(&mut buf)?;
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:18:5
+  --> $DIR/unused_io_amount.rs:19:5
    |
 LL |     s.write(b"test").unwrap();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -25,7 +25,7 @@ LL |     s.write(b"test").unwrap();
    = help: use `Write::write_all` instead, or handle partial writes
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:21:5
+  --> $DIR/unused_io_amount.rs:22:5
    |
 LL |     s.read(&mut buf).unwrap();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -33,19 +33,19 @@ LL |     s.read(&mut buf).unwrap();
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:26:5
+  --> $DIR/unused_io_amount.rs:27:5
    |
 LL |     s.read_vectored(&mut [io::IoSliceMut::new(&mut [])])?;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:28:5
+  --> $DIR/unused_io_amount.rs:29:5
    |
 LL |     s.write_vectored(&[io::IoSlice::new(&[])])?;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:36:5
+  --> $DIR/unused_io_amount.rs:37:5
    |
 LL |     reader.read(&mut result).ok()?;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -53,7 +53,7 @@ LL |     reader.read(&mut result).ok()?;
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:46:5
+  --> $DIR/unused_io_amount.rs:47:5
    |
 LL |     reader.read(&mut result).or_else(|err| Err(err))?;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -61,7 +61,7 @@ LL |     reader.read(&mut result).or_else(|err| Err(err))?;
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:59:5
+  --> $DIR/unused_io_amount.rs:60:5
    |
 LL |     reader.read(&mut result).or(Err(Error::Kind))?;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -69,7 +69,7 @@ LL |     reader.read(&mut result).or(Err(Error::Kind))?;
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:67:5
+  --> $DIR/unused_io_amount.rs:68:5
    |
 LL | /     reader
 LL | |
@@ -82,7 +82,7 @@ LL | |         .expect("error");
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:77:5
+  --> $DIR/unused_io_amount.rs:78:5
    |
 LL |     s.write(b"ok").is_ok();
    |     ^^^^^^^^^^^^^^^^^^^^^^
@@ -90,7 +90,7 @@ LL |     s.write(b"ok").is_ok();
    = help: use `Write::write_all` instead, or handle partial writes
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:79:5
+  --> $DIR/unused_io_amount.rs:80:5
    |
 LL |     s.write(b"err").is_err();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -98,7 +98,7 @@ LL |     s.write(b"err").is_err();
    = help: use `Write::write_all` instead, or handle partial writes
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:82:5
+  --> $DIR/unused_io_amount.rs:83:5
    |
 LL |     s.read(&mut buf).is_ok();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -106,7 +106,7 @@ LL |     s.read(&mut buf).is_ok();
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:84:5
+  --> $DIR/unused_io_amount.rs:85:5
    |
 LL |     s.read(&mut buf).is_err();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -114,7 +114,7 @@ LL |     s.read(&mut buf).is_err();
    = help: use `Read::read_exact` instead, or handle partial reads
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:89:5
+  --> $DIR/unused_io_amount.rs:90:5
    |
 LL |     w.write(b"hello world").await.unwrap();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -122,7 +122,7 @@ LL |     w.write(b"hello world").await.unwrap();
    = help: use `AsyncWriteExt::write_all` instead, or handle partial writes
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:95:5
+  --> $DIR/unused_io_amount.rs:96:5
    |
 LL |     r.read(&mut buf[..]).await.unwrap();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -130,7 +130,15 @@ LL |     r.read(&mut buf[..]).await.unwrap();
    = help: use `AsyncReadExt::read_exact` instead, or handle partial reads
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:109:9
+  --> $DIR/unused_io_amount.rs:104:5
+   |
+LL |     w.write(b"hello world");
+   |     ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: use `AsyncWriteExt::write_all` instead, or handle partial writes
+
+error: written amount is not handled
+  --> $DIR/unused_io_amount.rs:110:9
    |
 LL |         w.write(b"hello world").await?;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -138,7 +146,7 @@ LL |         w.write(b"hello world").await?;
    = help: use `AsyncWriteExt::write_all` instead, or handle partial writes
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:118:9
+  --> $DIR/unused_io_amount.rs:119:9
    |
 LL |         r.read(&mut buf[..]).await.or(Err(Error::Kind))?;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -146,7 +154,7 @@ LL |         r.read(&mut buf[..]).await.or(Err(Error::Kind))?;
    = help: use `AsyncReadExt::read_exact` instead, or handle partial reads
 
 error: written amount is not handled
-  --> $DIR/unused_io_amount.rs:127:5
+  --> $DIR/unused_io_amount.rs:128:5
    |
 LL |     w.write(b"hello world").await.unwrap();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -154,12 +162,103 @@ LL |     w.write(b"hello world").await.unwrap();
    = help: use `AsyncWriteExt::write_all` instead, or handle partial writes
 
 error: read amount is not handled
-  --> $DIR/unused_io_amount.rs:133:5
+  --> $DIR/unused_io_amount.rs:134:5
    |
 LL |     r.read(&mut buf[..]).await.unwrap();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: use `AsyncReadExt::read_exact` instead, or handle partial reads
 
-error: aborting due to 20 previous errors
+error: written amount is not handled
+  --> $DIR/unused_io_amount.rs:147:11
+   |
+LL |     match s.write(b"test") {
+   |           ^^^^^^^^^^^^^^^^
+   |
+   = help: use `Write::write_all` instead, or handle partial writes
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:149:9
+   |
+LL |         Ok(_) => todo!(),
+   |         ^^^^^^^^^^^^^^^^
+
+error: read amount is not handled
+  --> $DIR/unused_io_amount.rs:155:11
+   |
+LL |     match s.read(&mut buf) {
+   |           ^^^^^^^^^^^^^^^^
+   |
+   = help: use `Read::read_exact` instead, or handle partial reads
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:157:9
+   |
+LL |         Ok(_) => todo!(),
+   |         ^^^^^^^^^^^^^^^^
+
+error: read amount is not handled
+  --> $DIR/unused_io_amount.rs:164:11
+   |
+LL |     match s.read(&mut [0u8; 4]) {
+   |           ^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: use `Read::read_exact` instead, or handle partial reads
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:166:9
+   |
+LL |         Ok(_) => todo!(),
+   |         ^^^^^^^^^^^^^^^^
+
+error: written amount is not handled
+  --> $DIR/unused_io_amount.rs:173:11
+   |
+LL |     match s.write(b"test") {
+   |           ^^^^^^^^^^^^^^^^
+   |
+   = help: use `Write::write_all` instead, or handle partial writes
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:175:9
+   |
+LL |         Ok(_) => todo!(),
+   |         ^^^^^^^^^^^^^^^^
+
+error: read amount is not handled
+  --> $DIR/unused_io_amount.rs:186:8
+   |
+LL |     if let Ok(_) = s.read(&mut [0u8; 4]) {
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: use `Read::read_exact` instead, or handle partial reads
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:186:12
+   |
+LL |     if let Ok(_) = s.read(&mut [0u8; 4]) {
+   |            ^^^^^
+
+error: written amount is not handled
+  --> $DIR/unused_io_amount.rs:193:8
+   |
+LL |     if let Ok(_) = s.write(b"test") {
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: use `Write::write_all` instead, or handle partial writes
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:193:12
+   |
+LL |     if let Ok(_) = s.write(b"test") {
+   |            ^^^^^
+
+error: written amount is not handled
+  --> $DIR/unused_io_amount.rs:200:8
+   |
+LL |     if let Ok(..) = s.write(b"test") {
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: use `Write::write_all` instead, or handle partial writes
+note: the result is consumed here, but the amount of I/O bytes remains unhandled
+  --> $DIR/unused_io_amount.rs:200:12
+   |
+LL |     if let Ok(..) = s.write(b"test") {
+   |            ^^^^^^
+
+error: aborting due to 28 previous errors