about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/unused_io_amount.rs24
-rw-r--r--tests/ui/unused_io_amount.rs14
2 files changed, 37 insertions, 1 deletions
diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs
index cf406b817da..a8cc14d269a 100644
--- a/clippy_lints/src/unused_io_amount.rs
+++ b/clippy_lints/src/unused_io_amount.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::macros::{is_panic, root_macro_call_first_node};
-use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
+use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks};
 use hir::{ExprKind, HirId, PatKind};
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
@@ -83,6 +83,28 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
     /// 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>) {
+        let fn_def_id = block.hir_id.owner.to_def_id();
+        if let Some(impl_id) = cx.tcx.impl_of_method(fn_def_id)
+            && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id)
+        {
+            // We don't want to lint inside io::Read or io::Write implementations, as the author has more
+            // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836
+            if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) {
+                return;
+            }
+
+            let async_paths: [&[&str]; 4] = [
+                &paths::TOKIO_IO_ASYNCREADEXT,
+                &paths::TOKIO_IO_ASYNCWRITEEXT,
+                &paths::FUTURES_IO_ASYNCREADEXT,
+                &paths::FUTURES_IO_ASYNCWRITEEXT,
+            ];
+
+            if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) {
+                return;
+            }
+        }
+
         for stmt in block.stmts {
             if let hir::StmtKind::Semi(exp) = stmt.kind {
                 check_expr(cx, exp);
diff --git a/tests/ui/unused_io_amount.rs b/tests/ui/unused_io_amount.rs
index f5b200d5ffe..175c4ca7689 100644
--- a/tests/ui/unused_io_amount.rs
+++ b/tests/ui/unused_io_amount.rs
@@ -277,4 +277,18 @@ fn allow_works<F: std::io::Read>(mut f: F) {
     f.read(&mut data).unwrap();
 }
 
+struct Reader {}
+
+impl Read for Reader {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        todo!()
+    }
+
+    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
+        // We shouldn't recommend using Read::read_exact inside Read::read_exact!
+        self.read(buf).unwrap();
+        Ok(())
+    }
+}
+
 fn main() {}