Skip to content

Commit 29979ad

Browse files
committed
Auto merge of #6190 - montrivo:manual_result_unwrap_or, r=ebroto
manual_unwrap_or / support Result::unwrap_or Implements partially #5923. changelog: support Result::unwrap_or in manual_unwrap_or
2 parents 5c78d26 + 0d21ae0 commit 29979ad

File tree

5 files changed

+311
-37
lines changed

5 files changed

+311
-37
lines changed

clippy_lints/src/manual_unwrap_or.rs

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
use crate::consts::constant_simple;
22
use crate::utils;
3+
use crate::utils::sugg;
34
use if_chain::if_chain;
45
use rustc_errors::Applicability;
5-
use rustc_hir::{def, Arm, Expr, ExprKind, PatKind, QPath};
6+
use rustc_hir::{def, Arm, Expr, ExprKind, Pat, PatKind, QPath};
67
use rustc_lint::LintContext;
78
use rustc_lint::{LateContext, LateLintPass};
89
use rustc_middle::lint::in_external_macro;
910
use rustc_session::{declare_lint_pass, declare_tool_lint};
1011

1112
declare_clippy_lint! {
1213
/// **What it does:**
13-
/// Finds patterns that reimplement `Option::unwrap_or`.
14+
/// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
1415
///
1516
/// **Why is this bad?**
1617
/// Concise code helps focusing on behavior instead of boilerplate.
@@ -33,7 +34,7 @@ declare_clippy_lint! {
3334
/// ```
3435
pub MANUAL_UNWRAP_OR,
3536
complexity,
36-
"finds patterns that can be encoded more concisely with `Option::unwrap_or`"
37+
"finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
3738
}
3839

3940
declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]);
@@ -43,32 +44,50 @@ impl LateLintPass<'_> for ManualUnwrapOr {
4344
if in_external_macro(cx.sess(), expr.span) {
4445
return;
4546
}
46-
lint_option_unwrap_or_case(cx, expr);
47+
lint_manual_unwrap_or(cx, expr);
4748
}
4849
}
4950

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>> {
51+
#[derive(Copy, Clone)]
52+
enum Case {
53+
Option,
54+
Result,
55+
}
56+
57+
impl Case {
58+
fn unwrap_fn_path(&self) -> &str {
59+
match self {
60+
Case::Option => "Option::unwrap_or",
61+
Case::Result => "Result::unwrap_or",
62+
}
63+
}
64+
}
65+
66+
fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
67+
fn applicable_or_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
5268
if_chain! {
5369
if arms.len() == 2;
5470
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
71+
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)|
72+
match arm.pat.kind {
73+
PatKind::Path(ref some_qpath) =>
74+
utils::match_qpath(some_qpath, &utils::paths::OPTION_NONE),
75+
PatKind::TupleStruct(ref err_qpath, &[Pat { kind: PatKind::Wild, .. }], _) =>
76+
utils::match_qpath(err_qpath, &utils::paths::RESULT_ERR),
77+
_ => false,
6078
}
6179
);
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;
80+
let unwrap_arm = &arms[1 - idx];
81+
if let PatKind::TupleStruct(ref unwrap_qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind;
82+
if utils::match_qpath(unwrap_qpath, &utils::paths::OPTION_SOME)
83+
|| utils::match_qpath(unwrap_qpath, &utils::paths::RESULT_OK);
84+
if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
85+
if let ExprKind::Path(QPath::Resolved(_, body_path)) = unwrap_arm.body.kind;
6786
if let def::Res::Local(body_path_hir_id) = body_path.res;
6887
if body_path_hir_id == binding_hir_id;
69-
if !utils::usage::contains_return_break_continue_macro(none_arm.body);
88+
if !utils::usage::contains_return_break_continue_macro(or_arm.body);
7089
then {
71-
Some(none_arm)
90+
Some(or_arm)
7291
} else {
7392
None
7493
}
@@ -78,24 +97,29 @@ fn lint_option_unwrap_or_case<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tc
7897
if_chain! {
7998
if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind;
8099
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);
83-
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);
100+
if let Some(case) = if utils::is_type_diagnostic_item(cx, ty, sym!(option_type)) {
101+
Some(Case::Option)
102+
} else if utils::is_type_diagnostic_item(cx, ty, sym!(result_type)) {
103+
Some(Case::Result)
104+
} else {
105+
None
106+
};
107+
if let Some(or_arm) = applicable_or_arm(match_arms);
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));
90114
utils::span_lint_and_sugg(
91115
cx,
92116
MANUAL_UNWRAP_OR, expr.span,
93-
"this pattern reimplements `Option::unwrap_or`",
117+
&format!("this pattern reimplements `{}`", case.unwrap_fn_path()),
94118
"replace with",
95119
format!(
96120
"{}.unwrap_or({})",
97-
scrutinee_snippet,
98-
reindented_none_body,
121+
sugg::Sugg::hir(cx, scrutinee, "..").maybe_par(),
122+
reindented_or_body,
99123
),
100124
Applicability::MachineApplicable,
101125
);

src/lintlist/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1190,7 +1190,7 @@ vec![
11901190
Lint {
11911191
name: "manual_unwrap_or",
11921192
group: "complexity",
1193-
desc: "finds patterns that can be encoded more concisely with `Option::unwrap_or`",
1193+
desc: "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`",
11941194
deprecation: None,
11951195
module: "manual_unwrap_or",
11961196
},

tests/ui/manual_unwrap_or.fixed

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

4-
fn unwrap_or() {
5+
fn option_unwrap_or() {
56
// int case
67
Some(1).unwrap_or(42);
78

@@ -65,4 +66,74 @@ fn unwrap_or() {
6566
};
6667
}
6768

69+
fn result_unwrap_or() {
70+
// int case
71+
Ok::<i32, &str>(1).unwrap_or(42);
72+
73+
// int case, scrutinee is a binding
74+
let a = Ok::<i32, &str>(1);
75+
a.unwrap_or(42);
76+
77+
// int case, suggestion must surround Result expr with parenthesis
78+
(Ok(1) as Result<i32, &str>).unwrap_or(42);
79+
80+
// method call case, suggestion must not surround Result expr `s.method()` with parenthesis
81+
struct S {}
82+
impl S {
83+
fn method(self) -> Option<i32> {
84+
Some(42)
85+
}
86+
}
87+
let s = S {};
88+
s.method().unwrap_or(42);
89+
90+
// int case reversed
91+
Ok::<i32, &str>(1).unwrap_or(42);
92+
93+
// richer none expr
94+
Ok::<i32, &str>(1).unwrap_or(1 + 42);
95+
96+
// multiline case
97+
#[rustfmt::skip]
98+
Ok::<i32, &str>(1).unwrap_or({
99+
42 + 42
100+
+ 42 + 42 + 42
101+
+ 42 + 42 + 42
102+
});
103+
104+
// string case
105+
Ok::<&str, &str>("Bob").unwrap_or("Alice");
106+
107+
// don't lint
108+
match Ok::<i32, &str>(1) {
109+
Ok(i) => i + 2,
110+
Err(_) => 42,
111+
};
112+
match Ok::<i32, &str>(1) {
113+
Ok(i) => i,
114+
Err(_) => return,
115+
};
116+
for j in 0..4 {
117+
match Ok::<i32, &str>(j) {
118+
Ok(i) => i,
119+
Err(_) => continue,
120+
};
121+
match Ok::<i32, &str>(j) {
122+
Ok(i) => i,
123+
Err(_) => break,
124+
};
125+
}
126+
127+
// don't lint, Err value is used
128+
match Ok::<&str, &str>("Alice") {
129+
Ok(s) => s,
130+
Err(s) => s,
131+
};
132+
// could lint, but unused_variables takes care of it
133+
match Ok::<&str, &str>("Alice") {
134+
Ok(s) => s,
135+
Err(s) => "Bob",
136+
};
137+
}
138+
68139
fn main() {}

tests/ui/manual_unwrap_or.rs

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

4-
fn unwrap_or() {
5+
fn option_unwrap_or() {
56
// int case
67
match Some(1) {
78
Some(i) => i,
@@ -80,4 +81,98 @@ fn unwrap_or() {
8081
};
8182
}
8283

84+
fn result_unwrap_or() {
85+
// int case
86+
match Ok::<i32, &str>(1) {
87+
Ok(i) => i,
88+
Err(_) => 42,
89+
};
90+
91+
// int case, scrutinee is a binding
92+
let a = Ok::<i32, &str>(1);
93+
match a {
94+
Ok(i) => i,
95+
Err(_) => 42,
96+
};
97+
98+
// int case, suggestion must surround Result expr with parenthesis
99+
match Ok(1) as Result<i32, &str> {
100+
Ok(i) => i,
101+
Err(_) => 42,
102+
};
103+
104+
// method call case, suggestion must not surround Result expr `s.method()` with parenthesis
105+
struct S {}
106+
impl S {
107+
fn method(self) -> Option<i32> {
108+
Some(42)
109+
}
110+
}
111+
let s = S {};
112+
match s.method() {
113+
Some(i) => i,
114+
None => 42,
115+
};
116+
117+
// int case reversed
118+
match Ok::<i32, &str>(1) {
119+
Err(_) => 42,
120+
Ok(i) => i,
121+
};
122+
123+
// richer none expr
124+
match Ok::<i32, &str>(1) {
125+
Ok(i) => i,
126+
Err(_) => 1 + 42,
127+
};
128+
129+
// multiline case
130+
#[rustfmt::skip]
131+
match Ok::<i32, &str>(1) {
132+
Ok(i) => i,
133+
Err(_) => {
134+
42 + 42
135+
+ 42 + 42 + 42
136+
+ 42 + 42 + 42
137+
}
138+
};
139+
140+
// string case
141+
match Ok::<&str, &str>("Bob") {
142+
Ok(i) => i,
143+
Err(_) => "Alice",
144+
};
145+
146+
// don't lint
147+
match Ok::<i32, &str>(1) {
148+
Ok(i) => i + 2,
149+
Err(_) => 42,
150+
};
151+
match Ok::<i32, &str>(1) {
152+
Ok(i) => i,
153+
Err(_) => return,
154+
};
155+
for j in 0..4 {
156+
match Ok::<i32, &str>(j) {
157+
Ok(i) => i,
158+
Err(_) => continue,
159+
};
160+
match Ok::<i32, &str>(j) {
161+
Ok(i) => i,
162+
Err(_) => break,
163+
};
164+
}
165+
166+
// don't lint, Err value is used
167+
match Ok::<&str, &str>("Alice") {
168+
Ok(s) => s,
169+
Err(s) => s,
170+
};
171+
// could lint, but unused_variables takes care of it
172+
match Ok::<&str, &str>("Alice") {
173+
Ok(s) => s,
174+
Err(s) => "Bob",
175+
};
176+
}
177+
83178
fn main() {}

0 commit comments

Comments
 (0)