Skip to content

Commit 0f5606b

Browse files
committed
Most of remaining suggestions from flip1995
1 parent da6d807 commit 0f5606b

File tree

4 files changed

+49
-56
lines changed

4 files changed

+49
-56
lines changed

clippy_lints/src/option_if_let_else.rs

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::utils::sugg::Sugg;
2-
use crate::utils::{match_qpath, match_type, paths, span_lint_and_sugg};
2+
use crate::utils::{match_type, paths, span_lint_and_sugg};
33
use if_chain::if_chain;
44

55
use rustc_errors::Applicability;
@@ -10,40 +10,20 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
1010
declare_clippy_lint! {
1111
/// **What it does:**
1212
/// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
13-
/// idiomatically done with `Option::map_or` or `Option::map_or_else`.
13+
/// idiomatically done with `Option::map_or` (if the else bit is a simple
14+
/// expression) or `Option::map_or_else` (if the else bit is a longer
15+
/// block).
1416
///
1517
/// **Why is this bad?**
1618
/// Using the dedicated functions of the Option type is clearer and
1719
/// more concise than an if let expression.
1820
///
1921
/// **Known problems:**
20-
/// Using `Option::map_or` (as the lint currently suggests) requires
21-
/// it to compute the else value every function call, even if the
22-
/// value is unneeded. It might be better to use Option::map_or_else
23-
/// if the code is either very expensive or if it changes some state
24-
/// (i.e. mutates a value). For example,
25-
/// ```rust
26-
/// # let optional = Some(0);
27-
/// let _ = if let Some(x) = optional {
28-
/// x
29-
/// } else {
30-
/// let y = 0;
31-
/// /* Some expensive computation or state-changing operation */
32-
/// y
33-
/// };
34-
/// ```
35-
/// would be better as
36-
/// ```rust
37-
/// # let optional = Some(0);
38-
/// let _ = optional.map_or_else(|| { let y = 0; /* some expensive computation or
39-
/// state-changing operation */ y }, |x| x);
40-
/// ```
41-
/// instead of the suggested
42-
/// ```rust
43-
/// # let optional = Some(0);
44-
/// let _ = optional.map_or({ let y = 0; /* some expensive computation or state-changing
45-
/// operation */ y }, |x| x);
46-
/// ```
22+
/// This lint uses whether the block is just an expression or if it has
23+
/// more statements to decide whether to use `Option::map_or` or
24+
/// `Option::map_or_else`. If you have a single expression which calls
25+
/// an expensive function, then it would be more efficient to use
26+
/// `Option::map_or_else`, but this lint would suggest `Option::map_or`.
4727
///
4828
/// **Example:**
4929
///
@@ -54,13 +34,23 @@ declare_clippy_lint! {
5434
/// } else {
5535
/// 5
5636
/// };
37+
/// let _ = if let Some(foo) = optional {
38+
/// foo
39+
/// } else {
40+
/// let y = do_complicated_function();
41+
/// y*y
42+
/// };
5743
/// ```
5844
///
5945
/// should be
6046
///
6147
/// ```rust
6248
/// # let optional: Option<u32> = Some(0);
6349
/// let _ = optional.map_or(5, |foo| foo);
50+
/// let _ = optional.map_or_else(||{
51+
/// let y = do_complicated_function;
52+
/// y*y
53+
/// }, |foo| foo);
6454
/// ```
6555
pub OPTION_IF_LET_ELSE,
6656
style,
@@ -83,29 +73,32 @@ fn is_result_ok(cx: &LateContext<'_, '_>, expr: &'_ Expr<'_>) -> bool {
8373
}
8474
}
8575

76+
/// A struct containing information about occurences of the
77+
/// `if let Some(..) = .. else` construct that this lint detects.
78+
struct OptionIfLetElseOccurence {
79+
option: String,
80+
method_sugg: String,
81+
some_expr: String,
82+
none_expr: String,
83+
}
84+
8685
/// If this expression is the option if let/else construct we're detecting, then
87-
/// this function returns Some(Option<_> tested against, the method to call on
88-
/// the object (map_or or map_or_else), expression if Option is Some,
89-
/// expression if Option is None). Otherwise, it returns None.
86+
/// this function returns an OptionIfLetElseOccurence struct with details if
87+
/// this construct is found, or None if this construct is not found.
9088
fn detect_option_if_let_else<'a>(
9189
cx: &LateContext<'_, '_>,
9290
expr: &'a Expr<'_>,
93-
) -> Option<(String, String, String, String)> {
91+
) -> Option<OptionIfLetElseOccurence> { //(String, String, String, String)> {
9492
if_chain! {
9593
if let ExprKind::Match(let_body, arms, MatchSource::IfLetDesugar{contains_else_clause: true}) = &expr.kind;
9694
if arms.len() == 2;
9795
if match_type(cx, &cx.tables.expr_ty(let_body), &paths::OPTION);
98-
if !is_result_ok(cx, let_body);
99-
if let PatKind::TupleStruct(path, &[inner_pat], _) = &arms[0].pat.kind;
96+
if !is_result_ok(cx, let_body); // Don't lint on Result::ok because a different lint does it already
97+
if let PatKind::TupleStruct(_, &[inner_pat], _) = &arms[0].pat.kind;
10098
if let PatKind::Binding(_, _, id, _) = &inner_pat.kind;
10199
then {
102-
let (some_body, none_body) = if match_qpath(path, &paths::OPTION_SOME) {
103-
(arms[0].body, arms[1].body)
104-
} else {
105-
(arms[1].body, arms[0].body)
106-
};
107100
let some_body = if let ExprKind::Block(Block { stmts: statements, expr: Some(expr), .. }, _)
108-
= &some_body.kind {
101+
= &arms[0].body.kind {
109102
if let &[] = &statements {
110103
expr
111104
} else {
@@ -120,13 +113,13 @@ fn detect_option_if_let_else<'a>(
120113
}) {
121114
return None;
122115
}
123-
some_body
116+
&arms[0].body
124117
}
125118
} else {
126119
return None;
127120
};
128121
let (none_body, method_sugg) = if let ExprKind::Block(Block { stmts: statements, expr: Some(expr), .. }, _)
129-
= &none_body.kind {
122+
= &arms[1].body.kind {
130123
if let &[] = &statements {
131124
(expr, "map_or")
132125
} else {
@@ -140,18 +133,18 @@ fn detect_option_if_let_else<'a>(
140133
}) {
141134
return None;
142135
}
143-
(&none_body, "map_or_else")
136+
(&arms[1].body, "map_or_else")
144137
}
145138
} else {
146139
return None;
147140
};
148141
let capture_name = id.name.to_ident_string();
149-
Some((
150-
format!("{}", Sugg::hir(cx, let_body, "..")),
151-
format!("{}", method_sugg),
152-
format!("|{}| {}", capture_name, Sugg::hir(cx, some_body, "..")),
153-
format!("{}{}", if method_sugg == "map_or" { "" } else { "||" }, Sugg::hir(cx, none_body, ".."))
154-
))
142+
Some(OptionIfLetElseOccurence {
143+
option: format!("{}", Sugg::hir(cx, let_body, "..")),
144+
method_sugg: format!("{}", method_sugg),
145+
some_expr: format!("|{}| {}", capture_name, Sugg::hir(cx, some_body, "..")),
146+
none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "||" }, Sugg::hir(cx, none_body, ".."))
147+
})
155148
} else {
156149
None
157150
}
@@ -160,14 +153,14 @@ fn detect_option_if_let_else<'a>(
160153

161154
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OptionIfLetElse {
162155
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
163-
if let Some((option, method, map, else_func)) = detect_option_if_let_else(cx, expr) {
156+
if let Some(detection) = detect_option_if_let_else(cx, expr) {
164157
span_lint_and_sugg(
165158
cx,
166159
OPTION_IF_LET_ELSE,
167160
expr.span,
168-
format!("use Option::{} instead of an if let/else", method).as_str(),
161+
format!("use Option::{} instead of an if let/else", detection.method_sugg).as_str(),
169162
"try",
170-
format!("{}.{}({}, {})", option, method, else_func, map),
163+
format!("{}.{}({}, {})", detection.option, detection.method_sugg, detection.none_expr, detection.some_expr),
171164
Applicability::MachineApplicable,
172165
);
173166
}

tests/ui/author/blocks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![feature(stmt_expr_attributes)]
2-
#![allow(redundant_semicolon, clippy::no_effect)]
2+
#![allow(redundant_semicolons, clippy::no_effect)]
33

44
#[rustfmt::skip]
55
fn main() {

tests/ui/option_if_let_else.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
#![warn(clippy::option_if_let_else)]
21
// run-rustfix
2+
#![warn(clippy::option_if_let_else)]
33

44
fn bad1(string: Option<&str>) -> (bool, &str) {
55
string.map_or((false, "hello"), |x| (true, x))

tests/ui/option_if_let_else.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ help: try
4747
LL | let _ = arg.map_or_else(||{
4848
LL | let mut y = 1;
4949
LL | for _ in 0..10 {
50-
LL | y = (y + 2 / y) / 2;
50+
LL | y = (y + 2/y) / 2;
5151
LL | }
5252
LL | y
5353
...

0 commit comments

Comments
 (0)