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" | "is_ok" | "is_err" => {
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");