Skip to content

Commit ba9a648

Browse files
committed
Add .err().expect() lint
1 parent db5739a commit ba9a648

File tree

9 files changed

+137
-6
lines changed

9 files changed

+137
-6
lines changed
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use super::ERR_EXPECT;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::ty::implements_trait;
4+
use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item};
5+
use rustc_errors::Applicability;
6+
use rustc_lint::LateContext;
7+
use rustc_middle::ty;
8+
use rustc_middle::ty::Ty;
9+
use rustc_semver::RustcVersion;
10+
use rustc_span::{sym, Span};
11+
12+
pub(super) fn check(
13+
cx: &LateContext<'_>,
14+
_expr: &rustc_hir::Expr<'_>,
15+
recv: &rustc_hir::Expr<'_>,
16+
msrv: Option<&RustcVersion>,
17+
expect_span: Span,
18+
err_span: Span,
19+
) {
20+
if_chain! {
21+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
22+
// Test the version to make sure the lint can be showed (expect_err has been introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
23+
if meets_msrv(msrv, &msrvs::EXPECT_ERR);
24+
25+
// Grabs the `Result<T, E>` type
26+
let result_type = cx.typeck_results().expr_ty(recv);
27+
// Tests if the T type in a `Result<T, E>` is not None
28+
if let Some(data_type) = get_data_type(cx, result_type);
29+
// Tests if the T type in a `Result<T, E>` implements debug
30+
if has_debug_impl(data_type, cx);
31+
32+
then {
33+
span_lint_and_sugg(
34+
cx,
35+
ERR_EXPECT,
36+
err_span.to(expect_span),
37+
"called `.err().expect()` on a `Result` value",
38+
"try",
39+
"expect_err".to_string(),
40+
Applicability::MachineApplicable
41+
);
42+
}
43+
};
44+
}
45+
46+
/// Given a `Result<T, E>` type, return its data (`T`).
47+
fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
48+
match ty.kind() {
49+
ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().next(),
50+
_ => None,
51+
}
52+
}
53+
54+
/// Given a type, very if the Debug trait has been impl'd
55+
fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
56+
cx.tcx
57+
.get_diagnostic_item(sym::Debug)
58+
.map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
59+
}

clippy_lints/src/methods/mod.rs

+28
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod chars_next_cmp_with_unwrap;
99
mod clone_on_copy;
1010
mod clone_on_ref_ptr;
1111
mod cloned_instead_of_copied;
12+
mod err_expect;
1213
mod expect_fun_call;
1314
mod expect_used;
1415
mod extend_with_drain;
@@ -362,6 +363,29 @@ declare_clippy_lint! {
362363
"using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
363364
}
364365

366+
declare_clippy_lint! {
367+
/// ### What it does
368+
/// Checks for `.err().expect()` calls on the `Result` type.
369+
///
370+
/// ### Why is this bad?
371+
/// `.expect_err()` can be called directly to avoid the extra type conversion from `ok()`.
372+
///
373+
/// ### Example
374+
/// ```should_panic
375+
/// let x: Result<u32, &str> = Ok(10);
376+
/// x.err().expect("Testing err().expect()");
377+
/// ```
378+
/// Use instead:
379+
/// ```should_panic
380+
/// let x: Result<u32, &str> = Ok(10);
381+
/// x.expect_err("Testing expect_err");
382+
/// ```
383+
#[clippy::version = "1.61.0"]
384+
pub ERR_EXPECT,
385+
style,
386+
r#"using `.err().expect("")` when `.expect_err("")` can be used"#
387+
}
388+
365389
declare_clippy_lint! {
366390
/// ### What it does
367391
/// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
@@ -2165,6 +2189,7 @@ impl_lint_pass!(Methods => [
21652189
NEEDLESS_SPLITN,
21662190
UNNECESSARY_TO_OWNED,
21672191
UNNECESSARY_JOIN,
2192+
ERR_EXPECT,
21682193
]);
21692194

21702195
/// Extracts a method call name, args, and `Span` of the method name.
@@ -2428,8 +2453,10 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
24282453
},
24292454
("expect", [_]) => match method_call(recv) {
24302455
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
2456+
Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, msrv, span, err_span),
24312457
_ => expect_used::check(cx, expr, recv),
24322458
},
2459+
24332460
("extend", [arg]) => {
24342461
string_extend_chars::check(cx, expr, recv, arg);
24352462
extend_with_drain::check(cx, expr, recv, arg);
@@ -2571,6 +2598,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
25712598
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
25722599
},
25732600
},
2601+
25742602
_ => {},
25752603
}
25762604
}

clippy_lints/src/utils/conf.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ define_Conf! {
156156
///
157157
/// Suppress lints whenever the suggested change would cause breakage for other crates.
158158
(avoid_breaking_exported_api: bool = true),
159-
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS.
159+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, EXPECT_ERR.
160160
///
161161
/// The minimum rust version that the project supports
162162
(msrv: Option<String> = None),

clippy_utils/src/msrvs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ msrv_aliases! {
3030
1,34,0 { TRY_FROM }
3131
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
3232
1,28,0 { FROM_BOOL }
33-
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST }
33+
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
3434
1,16,0 { STR_REPEAT }
3535
}

tests/ui/err_expect.fixed

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// run-rustfix
2+
3+
struct MyTypeNonDebug;
4+
5+
#[derive(Debug)]
6+
struct MyTypeDebug;
7+
8+
fn main() {
9+
let test_debug: Result<MyTypeDebug, u32> = Ok(MyTypeDebug);
10+
test_debug.expect_err("Testing debug type");
11+
12+
let test_non_debug: Result<MyTypeNonDebug, u32> = Ok(MyTypeNonDebug);
13+
test_non_debug.err().expect("Testing non debug type");
14+
}

tests/ui/err_expect.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// run-rustfix
2+
3+
struct MyTypeNonDebug;
4+
5+
#[derive(Debug)]
6+
struct MyTypeDebug;
7+
8+
fn main() {
9+
let test_debug: Result<MyTypeDebug, u32> = Ok(MyTypeDebug);
10+
test_debug.err().expect("Testing debug type");
11+
12+
let test_non_debug: Result<MyTypeNonDebug, u32> = Ok(MyTypeNonDebug);
13+
test_non_debug.err().expect("Testing non debug type");
14+
}

tests/ui/err_expect.stderr

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: called `.err().expect()` on a `Result` value
2+
--> $DIR/err_expect.rs:10:16
3+
|
4+
LL | test_debug.err().expect("Testing debug type");
5+
| ^^^^^^^^^^^^ help: try: `expect_err`
6+
|
7+
= note: `-D clippy::err-expect` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

tests/ui/min_rust_version_attr.rs

+6
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ fn int_from_bool() -> u8 {
145145
true as u8
146146
}
147147

148+
fn err_expect() {
149+
let x: Result<u32, &str> = Ok(10);
150+
x.err().expect("Testing expect_err");
151+
}
152+
148153
fn main() {
149154
filter_map_next();
150155
checked_conversion();
@@ -162,6 +167,7 @@ fn main() {
162167
missing_const_for_fn();
163168
unnest_or_patterns();
164169
int_from_bool();
170+
err_expect();
165171
}
166172

167173
mod just_under_msrv {

tests/ui/min_rust_version_attr.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
error: stripping a prefix manually
2-
--> $DIR/min_rust_version_attr.rs:186:24
2+
--> $DIR/min_rust_version_attr.rs:192:24
33
|
44
LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
55
| ^^^^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::manual-strip` implied by `-D warnings`
88
note: the prefix was tested here
9-
--> $DIR/min_rust_version_attr.rs:185:9
9+
--> $DIR/min_rust_version_attr.rs:191:9
1010
|
1111
LL | if s.starts_with("hello, ") {
1212
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -17,13 +17,13 @@ LL ~ assert_eq!(<stripped>.to_uppercase(), "WORLD!");
1717
|
1818

1919
error: stripping a prefix manually
20-
--> $DIR/min_rust_version_attr.rs:198:24
20+
--> $DIR/min_rust_version_attr.rs:204:24
2121
|
2222
LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
2323
| ^^^^^^^^^^^^^^^^^^^^
2424
|
2525
note: the prefix was tested here
26-
--> $DIR/min_rust_version_attr.rs:197:9
26+
--> $DIR/min_rust_version_attr.rs:203:9
2727
|
2828
LL | if s.starts_with("hello, ") {
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)