Skip to content

Commit 9b6f866

Browse files
committed
Auto merge of rust-lang#12217 - PartiallyTyped:12208, r=blyxyas
Fixed FP in `unused_io_amount` for Ok(lit), unrachable! and unwrap de… …sugar Fixes fp caused by linting on Ok(_) for all cases outside binding. We introduce the following rules for match exprs. - `panic!` and `unreachable!` are treated as consumed. - `Ok( )` patterns outside `DotDot` and `Wild` are treated as consuming. changelog: FP [`unused_io_amount`] when matching Ok(literal) or unreachable fixes rust-lang#12208 r? `@blyxyas`
2 parents b58b88c + fe8c2e2 commit 9b6f866

File tree

3 files changed

+123
-29
lines changed

3 files changed

+123
-29
lines changed

clippy_lints/src/unused_io_amount.rs

+76-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths};
2+
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
3+
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
34
use hir::{ExprKind, PatKind};
45
use rustc_hir as hir;
56
use rustc_lint::{LateContext, LateLintPass};
@@ -82,37 +83,72 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
8283
}
8384

8485
if let Some(exp) = block.expr
85-
&& matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _))
86+
&& matches!(
87+
exp.kind,
88+
hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, hir::MatchSource::Normal)
89+
)
8690
{
8791
check_expr(cx, exp);
8892
}
8993
}
9094
}
9195

96+
fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
97+
// if there is a guard, we consider the result to be consumed
98+
if arm.guard.is_some() {
99+
return false;
100+
}
101+
if is_unreachable_or_panic(cx, arm.body) {
102+
// if the body is unreachable or there is a panic,
103+
// we consider the result to be consumed
104+
return false;
105+
}
106+
107+
if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind {
108+
return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr);
109+
}
110+
111+
false
112+
}
113+
114+
fn non_consuming_ok_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
115+
// if there is a guard, we consider the result to be consumed
116+
if arm.guard.is_some() {
117+
return false;
118+
}
119+
if is_unreachable_or_panic(cx, arm.body) {
120+
// if the body is unreachable or there is a panic,
121+
// we consider the result to be consumed
122+
return false;
123+
}
124+
125+
if is_ok_wild_or_dotdot_pattern(cx, arm.pat) {
126+
return true;
127+
}
128+
false
129+
}
130+
92131
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
93132
match expr.kind {
94133
hir::ExprKind::If(cond, _, _)
95134
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
96-
&& pattern_is_ignored_ok(cx, pat)
135+
&& is_ok_wild_or_dotdot_pattern(cx, pat)
97136
&& let Some(op) = should_lint(cx, init) =>
98137
{
99138
emit_lint(cx, cond.span, op, &[pat.span]);
100139
},
101-
hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
102-
let found_arms: Vec<_> = arms
103-
.iter()
104-
.filter_map(|arm| {
105-
if pattern_is_ignored_ok(cx, arm.pat) {
106-
Some(arm.span)
107-
} else {
108-
None
109-
}
110-
})
111-
.collect();
112-
if !found_arms.is_empty() {
113-
emit_lint(cx, expr.span, op, found_arms.as_slice());
140+
// we will capture only the case where the match is Ok( ) or Err( )
141+
// prefer to match the minimum possible, and expand later if needed
142+
// to avoid false positives on something as used as this
143+
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
144+
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
145+
emit_lint(cx, expr.span, op, &[arm1.pat.span]);
146+
}
147+
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
148+
emit_lint(cx, expr.span, op, &[arm2.pat.span]);
114149
}
115150
},
151+
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
116152
_ if let Some(op) = should_lint(cx, expr) => {
117153
emit_lint(cx, expr.span, op, &[]);
118154
},
@@ -130,25 +166,40 @@ fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option
130166
check_io_mode(cx, inner)
131167
}
132168

133-
fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool {
169+
fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> bool {
134170
// the if checks whether we are in a result Ok( ) pattern
135171
// and the return checks whether it is unhandled
136172

137-
if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind
173+
if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind
138174
// we check against Result::Ok to avoid linting on Err(_) or something else.
139175
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
140176
{
141-
return match (inner_pat, ddp.as_opt_usize()) {
142-
// Ok(_) pattern
143-
([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true,
144-
// Ok(..) pattern
145-
([], Some(0)) => true,
146-
_ => false,
147-
};
177+
if matches!(inner_pat, []) {
178+
return true;
179+
}
180+
181+
if let [cons_pat] = inner_pat
182+
&& matches!(cons_pat.kind, PatKind::Wild)
183+
{
184+
return true;
185+
}
186+
return false;
148187
}
149188
false
150189
}
151190

191+
// this is partially taken from panic_unimplemented
192+
fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
193+
let expr = peel_blocks(expr);
194+
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
195+
return false;
196+
};
197+
if is_panic(cx, macro_call.def_id) {
198+
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
199+
}
200+
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
201+
}
202+
152203
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
153204
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
154205
if matches!(

tests/ui/unused_io_amount.rs

+43
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,47 @@ fn on_return_should_not_raise<T: io::Read + io::Write>(s: &mut T) -> io::Result<
229229
s.read(&mut buf)
230230
}
231231

232+
pub fn unwrap_in_block(rdr: &mut dyn std::io::Read) -> std::io::Result<usize> {
233+
let read = { rdr.read(&mut [0])? };
234+
Ok(read)
235+
}
236+
237+
pub fn consumed_example(rdr: &mut dyn std::io::Read) {
238+
match rdr.read(&mut [0]) {
239+
Ok(0) => println!("EOF"),
240+
Ok(_) => println!("fully read"),
241+
Err(_) => println!("fail"),
242+
};
243+
match rdr.read(&mut [0]) {
244+
Ok(0) => println!("EOF"),
245+
Ok(_) => println!("fully read"),
246+
Err(_) => println!("fail"),
247+
}
248+
}
249+
250+
pub fn unreachable_or_panic(rdr: &mut dyn std::io::Read) {
251+
{
252+
match rdr.read(&mut [0]) {
253+
Ok(_) => unreachable!(),
254+
Err(_) => println!("expected"),
255+
}
256+
}
257+
258+
{
259+
match rdr.read(&mut [0]) {
260+
Ok(_) => panic!(),
261+
Err(_) => println!("expected"),
262+
}
263+
}
264+
}
265+
266+
pub fn wildcards(rdr: &mut dyn std::io::Read) {
267+
{
268+
match rdr.read(&mut [0]) {
269+
Ok(1) => todo!(),
270+
_ => todo!(),
271+
}
272+
}
273+
}
274+
232275
fn main() {}

tests/ui/unused_io_amount.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ note: the result is consumed here, but the amount of I/O bytes remains unhandled
180180
--> $DIR/unused_io_amount.rs:149:9
181181
|
182182
LL | Ok(_) => todo!(),
183-
| ^^^^^^^^^^^^^^^^
183+
| ^^^^^
184184

185185
error: read amount is not handled
186186
--> $DIR/unused_io_amount.rs:155:11
@@ -193,7 +193,7 @@ note: the result is consumed here, but the amount of I/O bytes remains unhandled
193193
--> $DIR/unused_io_amount.rs:157:9
194194
|
195195
LL | Ok(_) => todo!(),
196-
| ^^^^^^^^^^^^^^^^
196+
| ^^^^^
197197

198198
error: read amount is not handled
199199
--> $DIR/unused_io_amount.rs:164:11
@@ -206,7 +206,7 @@ note: the result is consumed here, but the amount of I/O bytes remains unhandled
206206
--> $DIR/unused_io_amount.rs:166:9
207207
|
208208
LL | Ok(_) => todo!(),
209-
| ^^^^^^^^^^^^^^^^
209+
| ^^^^^
210210

211211
error: written amount is not handled
212212
--> $DIR/unused_io_amount.rs:173:11
@@ -219,7 +219,7 @@ note: the result is consumed here, but the amount of I/O bytes remains unhandled
219219
--> $DIR/unused_io_amount.rs:175:9
220220
|
221221
LL | Ok(_) => todo!(),
222-
| ^^^^^^^^^^^^^^^^
222+
| ^^^^^
223223

224224
error: read amount is not handled
225225
--> $DIR/unused_io_amount.rs:186:8

0 commit comments

Comments
 (0)