Skip to content

Commit 038ab65

Browse files
committed
Don't trigger on blocks containing return, break, or continue
1 parent 48f1b17 commit 038ab65

File tree

4 files changed

+152
-22
lines changed

4 files changed

+152
-22
lines changed

clippy_lints/src/option_if_let_else.rs

+47-17
Original file line numberDiff line numberDiff line change
@@ -70,38 +70,68 @@ declare_clippy_lint! {
7070

7171
declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]);
7272

73-
/// If this expression is the option if let/else construct we're
74-
/// detecting, then this function returns Some(Option<_> compared,
75-
/// expression if Option is Some, expression if Option is None).
76-
/// Otherwise, it returns None.
77-
fn detect_option_if_let_else<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'_>) -> Option<(String, String, String)> {
73+
/// If this expression is the option if let/else construct we're detecting, then
74+
/// this function returns Some(Option<_> tested against, the method to call on
75+
/// the object (map_or or map_or_else), expression if Option is Some,
76+
/// expression if Option is None). Otherwise, it returns None.
77+
fn detect_option_if_let_else<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'_>) -> Option<(String, String, String, String)> {
7878
if_chain! {
7979
if let ExprKind::Match(let_body, arms, MatchSource::IfLetDesugar{contains_else_clause: true}) = &expr.kind;
8080
if arms.len() == 2;
8181
if match_type(cx, &cx.tables.expr_ty(let_body), &paths::OPTION);
8282
if let PatKind::TupleStruct(path, &[inner_pat], _) = &arms[0].pat.kind;
8383
if let PatKind::Binding(_, _, id, _) = &inner_pat.kind;
8484
then {
85-
let (mut some_body, mut none_body) = if match_qpath(path, &paths::OPTION_SOME) {
85+
let (some_body, none_body) = if match_qpath(path, &paths::OPTION_SOME) {
8686
(arms[0].body, arms[1].body)
8787
} else {
8888
(arms[1].body, arms[0].body)
8989
};
90-
some_body = if let ExprKind::Block(Block { stmts: &[], expr: Some(expr), .. }, _) = &some_body.kind {
91-
expr
90+
let some_body = if let ExprKind::Block(Block { stmts: statements, expr: Some(expr), .. }, _) = &some_body.kind {
91+
if let &[] = &statements {
92+
expr
93+
} else {
94+
// Don't trigger if there is a return, break, or continue inside
95+
if statements.iter().any(|statement| {
96+
match &statement {
97+
Stmt { kind: StmtKind::Semi(Expr { kind: ExprKind::Ret(..), ..}), .. } => true,
98+
Stmt { kind: StmtKind::Semi(Expr { kind: ExprKind::Break(..), ..}), .. } => true,
99+
Stmt { kind: StmtKind::Semi(Expr { kind: ExprKind::Continue(..), ..}), .. } => true,
100+
_ => false,
101+
}
102+
}) {
103+
return None;
104+
}
105+
some_body
106+
}
92107
} else {
93-
some_body
108+
return None;
94109
};
95-
none_body = if let ExprKind::Block(Block { stmts: &[], expr: Some(expr), .. }, _) = &none_body.kind {
96-
expr
110+
let (none_body, method_sugg) = if let ExprKind::Block(Block { stmts: statements, expr: Some(expr), .. }, _) = &none_body.kind {
111+
if let &[] = &statements {
112+
(expr, "map_or")
113+
} else {
114+
if statements.iter().any(|statement| {
115+
match &statement {
116+
Stmt { kind: StmtKind::Semi(Expr { kind: ExprKind::Ret(..), ..}), .. } => true,
117+
Stmt { kind: StmtKind::Semi(Expr { kind: ExprKind::Break(..), ..}), .. } => true,
118+
Stmt { kind: StmtKind::Semi(Expr { kind: ExprKind::Continue(..), ..}), .. } => true,
119+
_ => false,
120+
}
121+
}) {
122+
return None;
123+
}
124+
(&none_body, "map_or_else")
125+
}
97126
} else {
98-
none_body
127+
return None;
99128
};
100129
let capture_name = id.name.to_ident_string();
101130
Some((
102131
format!("{}", Sugg::hir(cx, let_body, "..")),
132+
format!("{}", method_sugg),
103133
format!("|{}| {}", capture_name, Sugg::hir(cx, some_body, "..")),
104-
format!("{}", Sugg::hir(cx, none_body, ".."))
134+
format!("{}{}", if method_sugg == "map_or" { "" } else { "||" }, Sugg::hir(cx, none_body, ".."))
105135
))
106136
} else {
107137
None
@@ -111,15 +141,15 @@ fn detect_option_if_let_else<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'_>) -
111141

112142
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OptionIfLetElse {
113143
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
114-
if let Some((option, map, else_func)) = detect_option_if_let_else(cx, expr) {
144+
if let Some((option, method, map, else_func)) = detect_option_if_let_else(cx, expr) {
115145
span_lint_and_sugg(
116146
cx,
117147
OPTION_IF_LET_ELSE,
118148
expr.span,
119-
"use Option::map_or instead of an if let/else",
149+
format!("use Option::{} instead of an if let/else", method).as_str(),
120150
"try",
121-
format!("{}.map_or({}, {})", option, else_func, map),
122-
Applicability::MaybeIncorrect,
151+
format!("{}.{}({}, {})", option, method, else_func, map),
152+
Applicability::MachineApplicable,
123153
);
124154
}
125155
}

tests/ui/option_if_let_else.fixed

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#![warn(clippy::option_if_let_else)]
2+
// run-rustfix
3+
4+
fn bad1(string: Option<&str>) -> (bool, &str) {
5+
string.map_or((false, "hello"), |x| (true, x))
6+
}
7+
8+
fn longer_body(arg: Option<u32>) -> u32 {
9+
arg.map_or(13, |x| {
10+
let y = x * x;
11+
y * y
12+
})
13+
}
14+
15+
fn test_map_or_else(arg: Option<u32>) {
16+
let _ = arg.map_or_else(||{
17+
let mut y = 1;
18+
for _ in 0..10 {
19+
y = (y + 2/y) / 2;
20+
}
21+
y
22+
}, |x| x * x * x * x);
23+
}
24+
25+
fn negative_tests(arg: Option<u32>) -> u32 {
26+
let _ = if let Some(13) = arg { "unlucky" } else { "lucky" };
27+
for _ in 0..10 {
28+
let _ = if let Some(x) = arg {
29+
x
30+
} else {
31+
continue;
32+
};
33+
}
34+
let _ = if let Some(x) = arg {
35+
return x;
36+
} else {
37+
5
38+
};
39+
7
40+
}
41+
42+
fn main() {
43+
let optional = Some(5);
44+
let _ = optional.map_or(5, |x| x + 2);
45+
let _ = bad1(None);
46+
let _ = longer_body(None);
47+
test_map_or_else(None);
48+
let _ = negative_tests(None);
49+
}

tests/ui/option_if_let_else.rs

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::option_if_let_else)]
2+
// run-rustfix
23

34
fn bad1(string: Option<&str>) -> (bool, &str) {
45
if let Some(x) = string {
@@ -17,13 +18,40 @@ fn longer_body(arg: Option<u32>) -> u32 {
1718
}
1819
}
1920

20-
fn negative_tests(arg: Option<u32>) {
21+
fn test_map_or_else(arg: Option<u32>) {
22+
let _ = if let Some(x) = arg {
23+
x * x * x * x
24+
} else {
25+
let mut y = 1;
26+
for _ in 0..10 {
27+
y = (y + 2/y) / 2;
28+
}
29+
y
30+
};
31+
}
32+
33+
fn negative_tests(arg: Option<u32>) -> u32 {
2134
let _ = if let Some(13) = arg { "unlucky" } else { "lucky" };
35+
for _ in 0..10 {
36+
let _ = if let Some(x) = arg {
37+
x
38+
} else {
39+
continue;
40+
};
41+
}
42+
let _ = if let Some(x) = arg {
43+
return x;
44+
} else {
45+
5
46+
};
47+
7
2248
}
2349

2450
fn main() {
2551
let optional = Some(5);
2652
let _ = if let Some(x) = optional { x + 2 } else { 5 };
2753
let _ = bad1(None);
2854
let _ = longer_body(None);
55+
test_map_or_else(None);
56+
let _ = negative_tests(None);
2957
}

tests/ui/option_if_let_else.stderr

+27-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: use Option::map_or instead of an if let/else
2-
--> $DIR/option_if_let_else.rs:4:5
2+
--> $DIR/option_if_let_else.rs:5:5
33
|
44
LL | / if let Some(x) = string {
55
LL | | (true, x)
@@ -11,7 +11,7 @@ LL | | }
1111
= note: `-D clippy::option-if-let-else` implied by `-D warnings`
1212

1313
error: use Option::map_or instead of an if let/else
14-
--> $DIR/option_if_let_else.rs:12:5
14+
--> $DIR/option_if_let_else.rs:13:5
1515
|
1616
LL | / if let Some(x) = arg {
1717
LL | | let y = x * x;
@@ -29,11 +29,34 @@ LL | y * y
2929
LL | })
3030
|
3131

32+
error: use Option::map_or_else instead of an if let/else
33+
--> $DIR/option_if_let_else.rs:22:13
34+
|
35+
LL | let _ = if let Some(x) = arg {
36+
| _____________^
37+
LL | | x * x * x * x
38+
LL | | } else {
39+
LL | | let mut y = 1;
40+
... |
41+
LL | | y
42+
LL | | };
43+
| |_____^
44+
|
45+
help: try
46+
|
47+
LL | let _ = arg.map_or_else(||{
48+
LL | let mut y = 1;
49+
LL | for _ in 0..10 {
50+
LL | y = (y + 2/y) / 2;
51+
LL | }
52+
LL | y
53+
...
54+
3255
error: use Option::map_or instead of an if let/else
33-
--> $DIR/option_if_let_else.rs:26:13
56+
--> $DIR/option_if_let_else.rs:52:13
3457
|
3558
LL | let _ = if let Some(x) = optional { x + 2 } else { 5 };
3659
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)`
3760

38-
error: aborting due to 3 previous errors
61+
error: aborting due to 4 previous errors
3962

0 commit comments

Comments
 (0)