Skip to content

Commit 915ce36

Browse files
committed
manual_unwrap_or / support Result::unwrap_or
1 parent 01dd31f commit 915ce36

File tree

5 files changed

+216
-31
lines changed

5 files changed

+216
-31
lines changed

clippy_lints/src/manual_unwrap_or.rs

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ use crate::consts::constant_simple;
22
use crate::utils;
33
use if_chain::if_chain;
44
use rustc_errors::Applicability;
5-
use rustc_hir::{def, Arm, Expr, ExprKind, PatKind, QPath};
5+
use rustc_hir::{def, Arm, Expr, ExprKind, Pat, PatKind, QPath};
66
use rustc_lint::LintContext;
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_middle::lint::in_external_macro;
99
use rustc_session::{declare_lint_pass, declare_tool_lint};
1010

1111
declare_clippy_lint! {
1212
/// **What it does:**
13-
/// Finds patterns that reimplement `Option::unwrap_or`.
13+
/// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
1414
///
1515
/// **Why is this bad?**
1616
/// Concise code helps focusing on behavior instead of boilerplate.
@@ -33,7 +33,7 @@ declare_clippy_lint! {
3333
/// ```
3434
pub MANUAL_UNWRAP_OR,
3535
complexity,
36-
"finds patterns that can be encoded more concisely with `Option::unwrap_or`"
36+
"finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
3737
}
3838

3939
declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]);
@@ -43,32 +43,50 @@ impl LateLintPass<'_> for ManualUnwrapOr {
4343
if in_external_macro(cx.sess(), expr.span) {
4444
return;
4545
}
46-
lint_option_unwrap_or_case(cx, expr);
46+
lint_manual_unwrap_or(cx, expr);
4747
}
4848
}
4949

50-
fn lint_option_unwrap_or_case<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
51-
fn applicable_none_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
50+
#[derive(Copy, Clone)]
51+
enum Case {
52+
Option,
53+
Result,
54+
}
55+
56+
impl Case {
57+
fn unwrap_fn_path(&self) -> &str {
58+
match self {
59+
Case::Option => "Option::unwrap_or",
60+
Case::Result => "Result::unwrap_or",
61+
}
62+
}
63+
}
64+
65+
fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
66+
fn applicable_or_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
5267
if_chain! {
5368
if arms.len() == 2;
5469
if arms.iter().all(|arm| arm.guard.is_none());
55-
if let Some((idx, none_arm)) = arms.iter().enumerate().find(|(_, arm)|
56-
if let PatKind::Path(ref qpath) = arm.pat.kind {
57-
utils::match_qpath(qpath, &utils::paths::OPTION_NONE)
58-
} else {
59-
false
70+
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)|
71+
match arm.pat.kind {
72+
PatKind::Path(ref some_qpath) =>
73+
utils::match_qpath(some_qpath, &utils::paths::OPTION_NONE),
74+
PatKind::TupleStruct(ref err_qpath, &[Pat { kind: PatKind::Wild, .. }], _) =>
75+
utils::match_qpath(err_qpath, &utils::paths::RESULT_ERR),
76+
_ => false,
6077
}
6178
);
62-
let some_arm = &arms[1 - idx];
63-
if let PatKind::TupleStruct(ref some_qpath, &[some_binding], _) = some_arm.pat.kind;
64-
if utils::match_qpath(some_qpath, &utils::paths::OPTION_SOME);
65-
if let PatKind::Binding(_, binding_hir_id, ..) = some_binding.kind;
66-
if let ExprKind::Path(QPath::Resolved(_, body_path)) = some_arm.body.kind;
79+
let unwrap_arm = &arms[1 - idx];
80+
if let PatKind::TupleStruct(ref unwrap_qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind;
81+
if utils::match_qpath(unwrap_qpath, &utils::paths::OPTION_SOME)
82+
|| utils::match_qpath(unwrap_qpath, &utils::paths::RESULT_OK);
83+
if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
84+
if let ExprKind::Path(QPath::Resolved(_, body_path)) = unwrap_arm.body.kind;
6785
if let def::Res::Local(body_path_hir_id) = body_path.res;
6886
if body_path_hir_id == binding_hir_id;
69-
if !utils::usage::contains_return_break_continue_macro(none_arm.body);
87+
if !utils::usage::contains_return_break_continue_macro(or_arm.body);
7088
then {
71-
Some(none_arm)
89+
Some(or_arm)
7290
} else {
7391
None
7492
}
@@ -78,24 +96,35 @@ fn lint_option_unwrap_or_case<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tc
7896
if_chain! {
7997
if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind;
8098
let ty = cx.typeck_results().expr_ty(scrutinee);
81-
if utils::is_type_diagnostic_item(cx, ty, sym!(option_type));
82-
if let Some(none_arm) = applicable_none_arm(match_arms);
99+
if let Some(case) = if utils::is_type_diagnostic_item(cx, ty, sym!(option_type)) {
100+
Some(Case::Option)
101+
} else if utils::is_type_diagnostic_item(cx, ty, sym!(result_type)) {
102+
Some(Case::Result)
103+
} else {
104+
None
105+
};
106+
if let Some(or_arm) = applicable_or_arm(match_arms);
83107
if let Some(scrutinee_snippet) = utils::snippet_opt(cx, scrutinee.span);
84-
if let Some(none_body_snippet) = utils::snippet_opt(cx, none_arm.body.span);
108+
if let Some(or_body_snippet) = utils::snippet_opt(cx, or_arm.body.span);
85109
if let Some(indent) = utils::indent_of(cx, expr.span);
86-
if constant_simple(cx, cx.typeck_results(), none_arm.body).is_some();
110+
if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
87111
then {
88-
let reindented_none_body =
89-
utils::reindent_multiline(none_body_snippet.into(), true, Some(indent));
112+
let reindented_or_body =
113+
utils::reindent_multiline(or_body_snippet.into(), true, Some(indent));
114+
let wrap_in_parens = !matches!(scrutinee, Expr { kind: ExprKind::Call(..), .. });
115+
let l_paren = if wrap_in_parens { "(" } else { "" };
116+
let r_paren = if wrap_in_parens { ")" } else { "" };
90117
utils::span_lint_and_sugg(
91118
cx,
92119
MANUAL_UNWRAP_OR, expr.span,
93-
"this pattern reimplements `Option::unwrap_or`",
120+
&format!("this pattern reimplements `{}`", case.unwrap_fn_path()),
94121
"replace with",
95122
format!(
96-
"{}.unwrap_or({})",
123+
"{}{}{}.unwrap_or({})",
124+
l_paren,
97125
scrutinee_snippet,
98-
reindented_none_body,
126+
r_paren,
127+
reindented_or_body,
99128
),
100129
Applicability::MachineApplicable,
101130
);

src/lintlist/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ vec![
11831183
Lint {
11841184
name: "manual_unwrap_or",
11851185
group: "complexity",
1186-
desc: "finds patterns that can be encoded more concisely with `Option::unwrap_or`",
1186+
desc: "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`",
11871187
deprecation: None,
11881188
module: "manual_unwrap_or",
11891189
},

tests/ui/manual_unwrap_or.fixed

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// run-rustfix
22
#![allow(dead_code)]
33

4-
fn unwrap_or() {
4+
fn option_unwrap_or() {
55
// int case
66
Some(1).unwrap_or(42);
77

@@ -65,4 +65,46 @@ fn unwrap_or() {
6565
};
6666
}
6767

68+
fn result_unwrap_or() {
69+
// int case
70+
(Ok(1) as Result<i32, &str>).unwrap_or(42);
71+
72+
// int case reversed
73+
(Ok(1) as Result<i32, &str>).unwrap_or(42);
74+
75+
// richer none expr
76+
(Ok(1) as Result<i32, &str>).unwrap_or(1 + 42);
77+
78+
// multiline case
79+
#[rustfmt::skip]
80+
(Ok(1) as Result<i32, &str>).unwrap_or({
81+
42 + 42
82+
+ 42 + 42 + 42
83+
+ 42 + 42 + 42
84+
});
85+
86+
// string case
87+
(Ok("Bob") as Result<&str, &str>).unwrap_or("Alice");
88+
89+
// don't lint
90+
match Ok(1) as Result<i32, &str> {
91+
Ok(i) => i + 2,
92+
Err(_) => 42,
93+
};
94+
match Ok(1) as Result<i32, &str> {
95+
Ok(i) => i,
96+
Err(_) => return,
97+
};
98+
for j in 0..4 {
99+
match Ok(j) as Result<i32, &str> {
100+
Ok(i) => i,
101+
Err(_) => continue,
102+
};
103+
match Ok(j) as Result<i32, &str> {
104+
Ok(i) => i,
105+
Err(_) => break,
106+
};
107+
}
108+
}
109+
68110
fn main() {}

tests/ui/manual_unwrap_or.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// run-rustfix
22
#![allow(dead_code)]
33

4-
fn unwrap_or() {
4+
fn option_unwrap_or() {
55
// int case
66
match Some(1) {
77
Some(i) => i,
@@ -80,4 +80,61 @@ fn unwrap_or() {
8080
};
8181
}
8282

83+
fn result_unwrap_or() {
84+
// int case
85+
match Ok(1) as Result<i32, &str> {
86+
Ok(i) => i,
87+
Err(_) => 42,
88+
};
89+
90+
// int case reversed
91+
match Ok(1) as Result<i32, &str> {
92+
Err(_) => 42,
93+
Ok(i) => i,
94+
};
95+
96+
// richer none expr
97+
match Ok(1) as Result<i32, &str> {
98+
Ok(i) => i,
99+
Err(_) => 1 + 42,
100+
};
101+
102+
// multiline case
103+
#[rustfmt::skip]
104+
match Ok(1) as Result<i32, &str> {
105+
Ok(i) => i,
106+
Err(_) => {
107+
42 + 42
108+
+ 42 + 42 + 42
109+
+ 42 + 42 + 42
110+
}
111+
};
112+
113+
// string case
114+
match Ok("Bob") as Result<&str, &str> {
115+
Ok(i) => i,
116+
Err(_) => "Alice",
117+
};
118+
119+
// don't lint
120+
match Ok(1) as Result<i32, &str> {
121+
Ok(i) => i + 2,
122+
Err(_) => 42,
123+
};
124+
match Ok(1) as Result<i32, &str> {
125+
Ok(i) => i,
126+
Err(_) => return,
127+
};
128+
for j in 0..4 {
129+
match Ok(j) as Result<i32, &str> {
130+
Ok(i) => i,
131+
Err(_) => continue,
132+
};
133+
match Ok(j) as Result<i32, &str> {
134+
Ok(i) => i,
135+
Err(_) => break,
136+
};
137+
}
138+
}
139+
83140
fn main() {}

tests/ui/manual_unwrap_or.stderr

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,62 @@ LL | | None => "Alice",
5757
LL | | };
5858
| |_____^ help: replace with: `Some("Bob").unwrap_or("Alice")`
5959

60-
error: aborting due to 5 previous errors
60+
error: this pattern reimplements `Result::unwrap_or`
61+
--> $DIR/manual_unwrap_or.rs:85:5
62+
|
63+
LL | / match Ok(1) as Result<i32, &str> {
64+
LL | | Ok(i) => i,
65+
LL | | Err(_) => 42,
66+
LL | | };
67+
| |_____^ help: replace with: `(Ok(1) as Result<i32, &str>).unwrap_or(42)`
68+
69+
error: this pattern reimplements `Result::unwrap_or`
70+
--> $DIR/manual_unwrap_or.rs:91:5
71+
|
72+
LL | / match Ok(1) as Result<i32, &str> {
73+
LL | | Err(_) => 42,
74+
LL | | Ok(i) => i,
75+
LL | | };
76+
| |_____^ help: replace with: `(Ok(1) as Result<i32, &str>).unwrap_or(42)`
77+
78+
error: this pattern reimplements `Result::unwrap_or`
79+
--> $DIR/manual_unwrap_or.rs:97:5
80+
|
81+
LL | / match Ok(1) as Result<i32, &str> {
82+
LL | | Ok(i) => i,
83+
LL | | Err(_) => 1 + 42,
84+
LL | | };
85+
| |_____^ help: replace with: `(Ok(1) as Result<i32, &str>).unwrap_or(1 + 42)`
86+
87+
error: this pattern reimplements `Result::unwrap_or`
88+
--> $DIR/manual_unwrap_or.rs:104:5
89+
|
90+
LL | / match Ok(1) as Result<i32, &str> {
91+
LL | | Ok(i) => i,
92+
LL | | Err(_) => {
93+
LL | | 42 + 42
94+
... |
95+
LL | | }
96+
LL | | };
97+
| |_____^
98+
|
99+
help: replace with
100+
|
101+
LL | (Ok(1) as Result<i32, &str>).unwrap_or({
102+
LL | 42 + 42
103+
LL | + 42 + 42 + 42
104+
LL | + 42 + 42 + 42
105+
LL | });
106+
|
107+
108+
error: this pattern reimplements `Result::unwrap_or`
109+
--> $DIR/manual_unwrap_or.rs:114:5
110+
|
111+
LL | / match Ok("Bob") as Result<&str, &str> {
112+
LL | | Ok(i) => i,
113+
LL | | Err(_) => "Alice",
114+
LL | | };
115+
| |_____^ help: replace with: `(Ok("Bob") as Result<&str, &str>).unwrap_or("Alice")`
116+
117+
error: aborting due to 10 previous errors
61118

0 commit comments

Comments
 (0)