Skip to content

Commit ad638a3

Browse files
committed
Auto merge of #4222 - jfrikker:try_err, r=flip1995
Adding try_err lint changelog: Adds the "try_err" lint, which catches instances of the following: Err(x)? fixes #4212
2 parents 58e6431 + c96bb2c commit ad638a3

File tree

9 files changed

+322
-2
lines changed

9 files changed

+322
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,7 @@ Released 2018-09-13
11341134
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
11351135
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
11361136
[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
1137+
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
11371138
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
11381139
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
11391140
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 305 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 306 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ pub mod temporary_assignment;
263263
pub mod transmute;
264264
pub mod transmuting_null;
265265
pub mod trivially_copy_pass_by_ref;
266+
pub mod try_err;
266267
pub mod types;
267268
pub mod unicode;
268269
pub mod unsafe_removed_from_name;
@@ -546,6 +547,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
546547
reg.register_early_lint_pass(box literal_representation::DecimalLiteralRepresentation::new(
547548
conf.literal_representation_threshold
548549
));
550+
reg.register_late_lint_pass(box try_err::TryErr);
549551
reg.register_late_lint_pass(box use_self::UseSelf);
550552
reg.register_late_lint_pass(box bytecount::ByteCount);
551553
reg.register_late_lint_pass(box infinite_iter::InfiniteIter);
@@ -861,6 +863,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
861863
transmute::WRONG_TRANSMUTE,
862864
transmuting_null::TRANSMUTING_NULL,
863865
trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF,
866+
try_err::TRY_ERR,
864867
types::ABSURD_EXTREME_COMPARISONS,
865868
types::BORROWED_BOX,
866869
types::BOX_VEC,
@@ -963,6 +966,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
963966
returns::NEEDLESS_RETURN,
964967
returns::UNUSED_UNIT,
965968
strings::STRING_LIT_AS_BYTES,
969+
try_err::TRY_ERR,
966970
types::FN_TO_NUMERIC_CAST,
967971
types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
968972
types::IMPLICIT_HASHER,

clippy_lints/src/try_err.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use crate::utils::{match_qpath, paths, snippet, span_lint_and_sugg};
2+
use if_chain::if_chain;
3+
use rustc::hir::*;
4+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
5+
use rustc::ty::Ty;
6+
use rustc::{declare_lint_pass, declare_tool_lint};
7+
use rustc_errors::Applicability;
8+
9+
declare_clippy_lint! {
10+
/// **What it does:** Checks for usages of `Err(x)?`.
11+
///
12+
/// **Why is this bad?** The `?` operator is designed to allow calls that
13+
/// can fail to be easily chained. For example, `foo()?.bar()` or
14+
/// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will
15+
/// always return), it is more clear to write `return Err(x)`.
16+
///
17+
/// **Known problems:** None.
18+
///
19+
/// **Example:**
20+
/// ```rust
21+
/// fn foo(fail: bool) -> Result<i32, String> {
22+
/// if fail {
23+
/// Err("failed")?;
24+
/// }
25+
/// Ok(0)
26+
/// }
27+
/// ```
28+
/// Could be written:
29+
///
30+
/// ```rust
31+
/// fn foo(fail: bool) -> Result<i32, String> {
32+
/// if fail {
33+
/// return Err("failed".into());
34+
/// }
35+
/// Ok(0)
36+
/// }
37+
/// ```
38+
pub TRY_ERR,
39+
style,
40+
"return errors explicitly rather than hiding them behind a `?`"
41+
}
42+
43+
declare_lint_pass!(TryErr => [TRY_ERR]);
44+
45+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TryErr {
46+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
47+
// Looks for a structure like this:
48+
// match ::std::ops::Try::into_result(Err(5)) {
49+
// ::std::result::Result::Err(err) =>
50+
// #[allow(unreachable_code)]
51+
// return ::std::ops::Try::from_error(::std::convert::From::from(err)),
52+
// ::std::result::Result::Ok(val) =>
53+
// #[allow(unreachable_code)]
54+
// val,
55+
// };
56+
if_chain! {
57+
if let ExprKind::Match(ref match_arg, _, MatchSource::TryDesugar) = expr.node;
58+
if let ExprKind::Call(ref match_fun, ref try_args) = match_arg.node;
59+
if let ExprKind::Path(ref match_fun_path) = match_fun.node;
60+
if match_qpath(match_fun_path, &paths::TRY_INTO_RESULT);
61+
if let Some(ref try_arg) = try_args.get(0);
62+
if let ExprKind::Call(ref err_fun, ref err_args) = try_arg.node;
63+
if let Some(ref err_arg) = err_args.get(0);
64+
if let ExprKind::Path(ref err_fun_path) = err_fun.node;
65+
if match_qpath(err_fun_path, &paths::RESULT_ERR);
66+
if let Some(return_type) = find_err_return_type(cx, &expr.node);
67+
68+
then {
69+
let err_type = cx.tables.expr_ty(err_arg);
70+
let suggestion = if err_type == return_type {
71+
format!("return Err({})", snippet(cx, err_arg.span, "_"))
72+
} else {
73+
format!("return Err({}.into())", snippet(cx, err_arg.span, "_"))
74+
};
75+
76+
span_lint_and_sugg(
77+
cx,
78+
TRY_ERR,
79+
expr.span,
80+
"returning an `Err(_)` with the `?` operator",
81+
"try this",
82+
suggestion,
83+
Applicability::MachineApplicable
84+
);
85+
}
86+
}
87+
}
88+
}
89+
90+
// In order to determine whether to suggest `.into()` or not, we need to find the error type the
91+
// function returns. To do that, we look for the From::from call (see tree above), and capture
92+
// its output type.
93+
fn find_err_return_type<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx ExprKind) -> Option<Ty<'tcx>> {
94+
if let ExprKind::Match(_, ref arms, MatchSource::TryDesugar) = expr {
95+
arms.iter().find_map(|ty| find_err_return_type_arm(cx, ty))
96+
} else {
97+
None
98+
}
99+
}
100+
101+
// Check for From::from in one of the match arms.
102+
fn find_err_return_type_arm<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arm: &'tcx Arm) -> Option<Ty<'tcx>> {
103+
if_chain! {
104+
if let ExprKind::Ret(Some(ref err_ret)) = arm.body.node;
105+
if let ExprKind::Call(ref from_error_path, ref from_error_args) = err_ret.node;
106+
if let ExprKind::Path(ref from_error_fn) = from_error_path.node;
107+
if match_qpath(from_error_fn, &paths::TRY_FROM_ERROR);
108+
if let Some(from_error_arg) = from_error_args.get(0);
109+
then {
110+
Some(cx.tables.expr_ty(from_error_arg))
111+
} else {
112+
None
113+
}
114+
}
115+
}

clippy_lints/src/utils/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"
107107
pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
108108
pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
109109
pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
110+
pub const TRY_FROM_ERROR: [&str; 4] = ["std", "ops", "Try", "from_error"];
110111
pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"];
111112
pub const UNINIT: [&str; 4] = ["core", "intrinsics", "", "uninit"];
112113
pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 305] = [
9+
pub const ALL_LINTS: [Lint; 306] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1820,6 +1820,13 @@ pub const ALL_LINTS: [Lint; 305] = [
18201820
deprecation: None,
18211821
module: "trivially_copy_pass_by_ref",
18221822
},
1823+
Lint {
1824+
name: "try_err",
1825+
group: "style",
1826+
desc: "return errors explicitly rather than hiding them behind a `?`",
1827+
deprecation: None,
1828+
module: "try_err",
1829+
},
18231830
Lint {
18241831
name: "type_complexity",
18251832
group: "complexity",

tests/ui/try_err.fixed

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// run-rustfix
2+
3+
#![deny(clippy::try_err)]
4+
5+
// Tests that a simple case works
6+
// Should flag `Err(err)?`
7+
pub fn basic_test() -> Result<i32, i32> {
8+
let err: i32 = 1;
9+
// To avoid warnings during rustfix
10+
if true {
11+
return Err(err);
12+
}
13+
Ok(0)
14+
}
15+
16+
// Tests that `.into()` is added when appropriate
17+
pub fn into_test() -> Result<i32, i32> {
18+
let err: u8 = 1;
19+
// To avoid warnings during rustfix
20+
if true {
21+
return Err(err.into());
22+
}
23+
Ok(0)
24+
}
25+
26+
// Tests that tries in general don't trigger the error
27+
pub fn negative_test() -> Result<i32, i32> {
28+
Ok(nested_error()? + 1)
29+
}
30+
31+
// Tests that `.into()` isn't added when the error type
32+
// matches the surrounding closure's return type, even
33+
// when it doesn't match the surrounding function's.
34+
pub fn closure_matches_test() -> Result<i32, i32> {
35+
let res: Result<i32, i8> = Some(1)
36+
.into_iter()
37+
.map(|i| {
38+
let err: i8 = 1;
39+
// To avoid warnings during rustfix
40+
if true {
41+
return Err(err);
42+
}
43+
Ok(i)
44+
})
45+
.next()
46+
.unwrap();
47+
48+
Ok(res?)
49+
}
50+
51+
// Tests that `.into()` isn't added when the error type
52+
// doesn't match the surrounding closure's return type.
53+
pub fn closure_into_test() -> Result<i32, i32> {
54+
let res: Result<i32, i16> = Some(1)
55+
.into_iter()
56+
.map(|i| {
57+
let err: i8 = 1;
58+
// To avoid warnings during rustfix
59+
if true {
60+
return Err(err.into());
61+
}
62+
Ok(i)
63+
})
64+
.next()
65+
.unwrap();
66+
67+
Ok(res?)
68+
}
69+
70+
fn nested_error() -> Result<i32, i32> {
71+
Ok(1)
72+
}
73+
74+
fn main() {
75+
basic_test().unwrap();
76+
into_test().unwrap();
77+
negative_test().unwrap();
78+
closure_matches_test().unwrap();
79+
closure_into_test().unwrap();
80+
}

tests/ui/try_err.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// run-rustfix
2+
3+
#![deny(clippy::try_err)]
4+
5+
// Tests that a simple case works
6+
// Should flag `Err(err)?`
7+
pub fn basic_test() -> Result<i32, i32> {
8+
let err: i32 = 1;
9+
// To avoid warnings during rustfix
10+
if true {
11+
Err(err)?;
12+
}
13+
Ok(0)
14+
}
15+
16+
// Tests that `.into()` is added when appropriate
17+
pub fn into_test() -> Result<i32, i32> {
18+
let err: u8 = 1;
19+
// To avoid warnings during rustfix
20+
if true {
21+
Err(err)?;
22+
}
23+
Ok(0)
24+
}
25+
26+
// Tests that tries in general don't trigger the error
27+
pub fn negative_test() -> Result<i32, i32> {
28+
Ok(nested_error()? + 1)
29+
}
30+
31+
// Tests that `.into()` isn't added when the error type
32+
// matches the surrounding closure's return type, even
33+
// when it doesn't match the surrounding function's.
34+
pub fn closure_matches_test() -> Result<i32, i32> {
35+
let res: Result<i32, i8> = Some(1)
36+
.into_iter()
37+
.map(|i| {
38+
let err: i8 = 1;
39+
// To avoid warnings during rustfix
40+
if true {
41+
Err(err)?;
42+
}
43+
Ok(i)
44+
})
45+
.next()
46+
.unwrap();
47+
48+
Ok(res?)
49+
}
50+
51+
// Tests that `.into()` isn't added when the error type
52+
// doesn't match the surrounding closure's return type.
53+
pub fn closure_into_test() -> Result<i32, i32> {
54+
let res: Result<i32, i16> = Some(1)
55+
.into_iter()
56+
.map(|i| {
57+
let err: i8 = 1;
58+
// To avoid warnings during rustfix
59+
if true {
60+
Err(err)?;
61+
}
62+
Ok(i)
63+
})
64+
.next()
65+
.unwrap();
66+
67+
Ok(res?)
68+
}
69+
70+
fn nested_error() -> Result<i32, i32> {
71+
Ok(1)
72+
}
73+
74+
fn main() {
75+
basic_test().unwrap();
76+
into_test().unwrap();
77+
negative_test().unwrap();
78+
closure_matches_test().unwrap();
79+
closure_into_test().unwrap();
80+
}

tests/ui/try_err.stderr

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: returning an `Err(_)` with the `?` operator
2+
--> $DIR/try_err.rs:11:9
3+
|
4+
LL | Err(err)?;
5+
| ^^^^^^^^^ help: try this: `return Err(err)`
6+
|
7+
note: lint level defined here
8+
--> $DIR/try_err.rs:3:9
9+
|
10+
LL | #![deny(clippy::try_err)]
11+
| ^^^^^^^^^^^^^^^
12+
13+
error: returning an `Err(_)` with the `?` operator
14+
--> $DIR/try_err.rs:21:9
15+
|
16+
LL | Err(err)?;
17+
| ^^^^^^^^^ help: try this: `return Err(err.into())`
18+
19+
error: returning an `Err(_)` with the `?` operator
20+
--> $DIR/try_err.rs:41:17
21+
|
22+
LL | Err(err)?;
23+
| ^^^^^^^^^ help: try this: `return Err(err)`
24+
25+
error: returning an `Err(_)` with the `?` operator
26+
--> $DIR/try_err.rs:60:17
27+
|
28+
LL | Err(err)?;
29+
| ^^^^^^^^^ help: try this: `return Err(err.into())`
30+
31+
error: aborting due to 4 previous errors
32+

0 commit comments

Comments
 (0)