1 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
2 use clippy_utils::{is_trait_method, is_try, match_trait_method, paths};
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 /// Checks for unused written/read amount.
12 /// ### Why is this bad?
13 /// `io::Write::write(_vectored)` and
14 /// `io::Read::read(_vectored)` are not guaranteed to
15 /// process the entire buffer. They return how many bytes were processed, which
17 /// than a given buffer's length. If you don't need to deal with
18 /// partial-write/read, use
19 /// `write_all`/`read_exact` instead.
21 /// When working with asynchronous code (either with the `futures`
22 /// crate or with `tokio`), a similar issue exists for
23 /// `AsyncWriteExt::write()` and `AsyncReadExt::read()` : these
24 /// functions are also not guaranteed to process the entire
25 /// buffer. Your code should either handle partial-writes/reads, or
26 /// call the `write_all`/`read_exact` methods on those traits instead.
28 /// ### Known problems
29 /// Detects only common patterns.
34 /// fn foo<W: io::Write>(w: &mut W) -> io::Result<()> {
35 /// // must be `w.write_all(b"foo")?;`
40 #[clippy::version = "pre 1.29.0"]
43 "unused written/read amount"
46 declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
48 impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
49 fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
50 let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = s.kind else {
55 hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
56 if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
59 hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
61 check_map_error(cx, arg_0, expr);
64 check_map_error(cx, res, expr);
67 hir::ExprKind::MethodCall(path, arg_0, ..) => match path.ident.as_str() {
68 "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => {
69 check_map_error(cx, arg_0, expr);
78 /// If `expr` is an (e).await, return the inner expression "e" that's being
79 /// waited on. Otherwise return None.
80 fn try_remove_await<'a>(expr: &'a hir::Expr<'a>) -> Option<&hir::Expr<'a>> {
81 if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
82 if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
85 hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
95 fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
97 while let hir::ExprKind::MethodCall(path, receiver, ..) = call.kind {
98 if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
105 if let Some(call) = try_remove_await(call) {
106 check_method_call(cx, call, expr, true);
108 check_method_call(cx, call, expr, false);
112 fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>, is_await: bool) {
113 if let hir::ExprKind::MethodCall(path, ..) = call.kind {
114 let symbol = path.ident.as_str();
115 let read_trait = if is_await {
116 match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
117 || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT)
119 is_trait_method(cx, call, sym::IoRead)
121 let write_trait = if is_await {
122 match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT)
123 || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
125 is_trait_method(cx, call, sym::IoWrite)
128 match (read_trait, write_trait, symbol, is_await) {
129 (true, _, "read", false) => span_lint_and_help(
133 "read amount is not handled",
135 "use `Read::read_exact` instead, or handle partial reads",
137 (true, _, "read", true) => span_lint_and_help(
141 "read amount is not handled",
143 "use `AsyncReadExt::read_exact` instead, or handle partial reads",
145 (true, _, "read_vectored", _) => {
146 span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled");
148 (_, true, "write", false) => span_lint_and_help(
152 "written amount is not handled",
154 "use `Write::write_all` instead, or handle partial writes",
156 (_, true, "write", true) => span_lint_and_help(
160 "written amount is not handled",
162 "use `AsyncWriteExt::write_all` instead, or handle partial writes",
164 (_, true, "write_vectored", _) => {
165 span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled");