Skip to content

Commit 0a6e3ae

Browse files
committed
Make the lint more specialized
1 parent ca4c3ee commit 0a6e3ae

File tree

3 files changed

+137
-92
lines changed

3 files changed

+137
-92
lines changed

clippy_lints/src/let_else_on_result_ok.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
use clippy_utils::{
2-
diagnostics::span_lint_and_note,
3-
is_from_proc_macro, is_lang_item_or_ctor,
2+
diagnostics::span_lint_and_then,
3+
is_from_proc_macro, is_path_diagnostic_item,
44
msrvs::{self, Msrv},
5+
ty::is_type_diagnostic_item,
6+
visitors::for_each_expr,
57
};
6-
use rustc_hir::{LangItem, PatKind, Stmt, StmtKind};
8+
use rustc_hir::{ExprKind, FnRetTy, ItemKind, OwnerNode, Stmt, StmtKind};
79
use rustc_lint::{LateContext, LateLintPass, LintContext};
8-
use rustc_middle::lint::in_external_macro;
10+
use rustc_middle::{lint::in_external_macro, ty};
911
use rustc_session::{declare_tool_lint, impl_lint_pass};
12+
use rustc_span::sym;
13+
use std::ops::ControlFlow;
1014

1115
declare_clippy_lint! {
1216
/// ### What it does
13-
/// Checks for the usage of `Ok` in a `let...else` statement.
17+
/// Checks for the usage of `Ok` in a `let...else` statement in a function that returns
18+
/// `Result`.
1419
///
1520
/// ### Why is this bad?
1621
/// This will ignore the contents of the `Err` variant, which is generally unintended and not
@@ -48,7 +53,7 @@ declare_clippy_lint! {
4853
/// # fn foo() -> Result<(), ()> {
4954
/// # Err(())
5055
/// # }
51-
/// // If you want to ignore the contents of the `Err` variant:
56+
/// // If you want to explicitly ignore the contents of the `Err` variant:
5257
/// let Some(foo) = foo().ok() else {
5358
/// return;
5459
/// };
@@ -77,13 +82,32 @@ impl<'tcx> LateLintPass<'tcx> for LetElseOnResultOk {
7782
return;
7883
}
7984

80-
if let StmtKind::Local(local) = stmt.kind && let Some(els) = local.els {
85+
if let StmtKind::Local(local) = stmt.kind
86+
&& let Some(els) = local.els
87+
&& let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(stmt.hir_id))
88+
&& let ItemKind::Fn(sig, _, _) = item.kind
89+
&& let FnRetTy::Return(ret_ty) = sig.decl.output
90+
&& is_path_diagnostic_item(cx, ret_ty, sym::Result)
91+
// Only lint if we return from it
92+
&& for_each_expr(els, |expr| {
93+
if matches!(expr.kind, ExprKind::Ret(..)) {
94+
return ControlFlow::Break(());
95+
}
96+
97+
ControlFlow::Continue(())
98+
})
99+
.is_some()
100+
{
81101
let spans = {
82102
let mut spans = vec![];
83103
local.pat.walk_always(|pat| {
84-
if let PatKind::TupleStruct(qpath, _, _) = pat.kind
85-
&& let Some(def_id) = cx.qpath_res(&qpath, pat.hir_id).opt_def_id()
86-
&& is_lang_item_or_ctor(cx, def_id, LangItem::ResultOk)
104+
let ty = cx.typeck_results().pat_ty(pat);
105+
if is_type_diagnostic_item(cx, ty, sym::Result)
106+
&& let ty::Adt(_, substs) = ty.kind()
107+
&& let [_, err_ty] = substs.as_slice()
108+
&& let Some(err_ty) = err_ty.as_type()
109+
&& let Some(err_def) = err_ty.ty_adt_def()
110+
&& err_def.all_fields().count() != 0
87111
{
88112
spans.push(pat.span);
89113
}
@@ -96,13 +120,15 @@ impl<'tcx> LateLintPass<'tcx> for LetElseOnResultOk {
96120
};
97121

98122
for span in spans {
99-
span_lint_and_note(
123+
span_lint_and_then(
100124
cx,
101125
LET_ELSE_ON_RESULT_OK,
102126
span,
103-
"usage of `let...else` on `Err`",
104-
None,
105-
"consider handling the `Err` variant gracefully or propagating it to the caller",
127+
"usage of `let...else` on `Ok`",
128+
|diag| {
129+
diag.note("this will ignore the contents of the `Err` variant");
130+
diag.help("consider using a `match` instead, or propagating it to the caller");
131+
}
106132
);
107133
}
108134
}

tests/ui/let_else_on_result_ok.rs

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,52 @@ enum AnEnum {
1111
A(Result<&'static A, ()>),
1212
}
1313

14+
struct B;
15+
16+
struct C(u32);
17+
18+
enum D {
19+
A,
20+
B,
21+
}
22+
23+
enum E {
24+
A,
25+
B(u32),
26+
}
27+
28+
#[repr(C)]
29+
union F {
30+
a: u32,
31+
}
32+
1433
fn a() -> Result<(), ()> {
1534
Ok(())
1635
}
1736

37+
fn b() -> Result<(), ()> {
38+
let (Ok(_), 1) = (a(), 1) else {
39+
return Err::<(), _>(());
40+
};
41+
Ok(())
42+
}
43+
44+
fn c() -> Result<(), C> {
45+
todo!()
46+
}
47+
48+
fn d() -> Result<(), D> {
49+
todo!()
50+
}
51+
52+
fn e() -> Result<(), E> {
53+
todo!()
54+
}
55+
56+
fn f() -> Result<(), F> {
57+
todo!()
58+
}
59+
1860
fn a_constructor() -> A {
1961
todo!();
2062
}
@@ -23,34 +65,56 @@ fn an_enum_constructor() -> AnEnum {
2365
todo!();
2466
}
2567

26-
fn main() {
68+
fn main() -> Result<(), Box<dyn std::error::Error>> {
2769
// Lint
28-
let Ok(_) = a() else {
29-
return;
70+
let Ok(_) = c() else {
71+
return Ok(());
3072
};
31-
let (Ok(_), true) = (a(), true) else {
32-
return;
73+
let Ok(_) = e() else {
74+
return Ok(());
3375
};
34-
let [Ok(_), Ok(_)] = [a(), Err(())] else {
35-
return;
36-
};
37-
let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
38-
return;
39-
};
40-
let AnEnum::A(Ok(A(Err(_)))) = an_enum_constructor() else {
41-
return;
76+
let Ok(_) = f() else {
77+
return Ok(());
4278
};
4379
// Don't lint
80+
loop {
81+
let Ok(_) = c() else {
82+
continue;
83+
};
84+
}
4485
let Err(_) = a() else {
45-
return;
86+
return Ok(());
87+
};
88+
let Ok(_) = a() else {
89+
return Ok(());
90+
};
91+
let Ok(_) = b() else {
92+
return Ok(());
93+
};
94+
let Ok(_) = d() else {
95+
return Ok(());
4696
};
4797
match a() {
4898
Ok(a) => a,
4999
Err(e) => eprintln!("{e:#?}"),
50100
};
51101
external! {
52102
let Ok(_) = a() else {
53-
return;
103+
return Ok(());
54104
};
55105
}
106+
Ok(())
107+
}
108+
109+
fn no_result_main() {
110+
// Don't lint
111+
let Ok(_) = c() else {
112+
return;
113+
};
114+
let Ok(_) = e() else {
115+
return;
116+
};
117+
let Ok(_) = f() else {
118+
return;
119+
};
56120
}

tests/ui/let_else_on_result_ok.stderr

Lines changed: 18 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,30 @@
1-
error: usage of `let...else` on `Err`
2-
--> $DIR/let_else_on_result_ok.rs:28:9
1+
error: usage of `let...else` on `Ok`
2+
--> $DIR/let_else_on_result_ok.rs:70:9
33
|
4-
LL | let Ok(_) = a() else {
4+
LL | let Ok(_) = c() else {
55
| ^^^^^
66
|
7-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
7+
= note: this will ignore the contents of the `Err` variant
8+
= help: consider using a `match` instead, or propagating it to the caller
89
= note: `-D clippy::let-else-on-result-ok` implied by `-D warnings`
910

10-
error: usage of `let...else` on `Err`
11-
--> $DIR/let_else_on_result_ok.rs:31:10
11+
error: usage of `let...else` on `Ok`
12+
--> $DIR/let_else_on_result_ok.rs:73:9
1213
|
13-
LL | let (Ok(_), true) = (a(), true) else {
14-
| ^^^^^
15-
|
16-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
17-
18-
error: usage of `let...else` on `Err`
19-
--> $DIR/let_else_on_result_ok.rs:34:10
20-
|
21-
LL | let [Ok(_), Ok(_)] = [a(), Err(())] else {
22-
| ^^^^^
23-
|
24-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
25-
26-
error: usage of `let...else` on `Err`
27-
--> $DIR/let_else_on_result_ok.rs:34:17
28-
|
29-
LL | let [Ok(_), Ok(_)] = [a(), Err(())] else {
30-
| ^^^^^
31-
|
32-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
33-
34-
error: usage of `let...else` on `Err`
35-
--> $DIR/let_else_on_result_ok.rs:37:11
36-
|
37-
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
38-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
39-
|
40-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
41-
42-
error: usage of `let...else` on `Err`
43-
--> $DIR/let_else_on_result_ok.rs:37:16
44-
|
45-
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
46-
| ^^^^^^^^^^^^^^^^^^^
47-
|
48-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
49-
50-
error: usage of `let...else` on `Err`
51-
--> $DIR/let_else_on_result_ok.rs:37:21
52-
|
53-
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
54-
| ^^^^^^^^^^^^
55-
|
56-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
57-
58-
error: usage of `let...else` on `Err`
59-
--> $DIR/let_else_on_result_ok.rs:37:26
60-
|
61-
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
62-
| ^^^^^
14+
LL | let Ok(_) = e() else {
15+
| ^^^^^
6316
|
64-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
17+
= note: this will ignore the contents of the `Err` variant
18+
= help: consider using a `match` instead, or propagating it to the caller
6519

66-
error: usage of `let...else` on `Err`
67-
--> $DIR/let_else_on_result_ok.rs:40:19
20+
error: usage of `let...else` on `Ok`
21+
--> $DIR/let_else_on_result_ok.rs:76:9
6822
|
69-
LL | let AnEnum::A(Ok(A(Err(_)))) = an_enum_constructor() else {
70-
| ^^^^^^^^^^^^^
23+
LL | let Ok(_) = f() else {
24+
| ^^^^^
7125
|
72-
= note: consider handling the `Err` variant gracefully or propagating it to the caller
26+
= note: this will ignore the contents of the `Err` variant
27+
= help: consider using a `match` instead, or propagating it to the caller
7328

74-
error: aborting due to 9 previous errors
29+
error: aborting due to 3 previous errors
7530

0 commit comments

Comments
 (0)