Skip to content

Commit 60a8084

Browse files
committed
Adding try_err lint
1 parent c5d1ecd commit 60a8084

File tree

7 files changed

+231
-2
lines changed

7 files changed

+231
-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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use crate::utils::{match_qpath, paths, snippet, span_lint_and_then};
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+
///
21+
/// ```rust,ignore
22+
/// // Bad
23+
/// fn foo(fail: bool) -> Result<i32, String> {
24+
/// if fail {
25+
/// Err("failed")?;
26+
/// }
27+
/// Ok(0)
28+
/// }
29+
///
30+
/// // Good
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, &["std", "ops", "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_then(
77+
cx,
78+
TRY_ERR,
79+
expr.span,
80+
&format!("confusing error return, consider using `{}`", suggestion),
81+
|db| {
82+
db.span_suggestion(
83+
expr.span,
84+
"try this",
85+
suggestion,
86+
Applicability::MaybeIncorrect
87+
);
88+
},
89+
);
90+
}
91+
}
92+
}
93+
}
94+
95+
// In order to determine whether to suggest `.into()` or not, we need to find the error type the
96+
// function returns. To do that, we look for the From::from call (see tree above), and capture
97+
// its output type.
98+
fn find_err_return_type<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx ExprKind) -> Option<Ty<'tcx>> {
99+
if let ExprKind::Match(_, ref arms, MatchSource::TryDesugar) = expr {
100+
arms.iter().filter_map(|ty| find_err_return_type_arm(cx, ty)).nth(0)
101+
} else {
102+
None
103+
}
104+
}
105+
106+
// Check for From::from in one of the match arms.
107+
fn find_err_return_type_arm<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arm: &'tcx Arm) -> Option<Ty<'tcx>> {
108+
if_chain! {
109+
if let ExprKind::Ret(Some(ref err_ret)) = arm.body.node;
110+
if let ExprKind::Call(ref from_error_path, ref from_error_args) = err_ret.node;
111+
if let ExprKind::Path(ref from_error_fn) = from_error_path.node;
112+
if match_qpath(from_error_fn, &["std", "ops", "Try", "from_error"]);
113+
if let Some(from_error_arg) = from_error_args.get(0);
114+
then {
115+
Some(cx.tables.expr_ty(from_error_arg))
116+
} else {
117+
None
118+
}
119+
}
120+
}

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: "TODO",
1827+
deprecation: None,
1828+
module: "try_err",
1829+
},
18231830
Lint {
18241831
name: "type_complexity",
18251832
group: "complexity",

tests/ui/try_err.rs

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

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: confusing error return, consider using `return Err(err)`
2+
--> $DIR/try_err.rs:7:5
3+
|
4+
LL | Err(err)?;
5+
| ^^^^^^^^^ help: try this: `return Err(err)`
6+
|
7+
note: lint level defined here
8+
--> $DIR/try_err.rs:1:9
9+
|
10+
LL | #![deny(clippy::try_err)]
11+
| ^^^^^^^^^^^^^^^
12+
13+
error: confusing error return, consider using `return Err(err.into())`
14+
--> $DIR/try_err.rs:14:5
15+
|
16+
LL | Err(err)?;
17+
| ^^^^^^^^^ help: try this: `return Err(err.into())`
18+
19+
error: confusing error return, consider using `return Err(err)`
20+
--> $DIR/try_err.rs:31:13
21+
|
22+
LL | Err(err)?;
23+
| ^^^^^^^^^ help: try this: `return Err(err)`
24+
25+
error: confusing error return, consider using `return Err(err.into())`
26+
--> $DIR/try_err.rs:46:13
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)