Skip to content

Commit ac85692

Browse files
committed
Auto merge of #5301 - JarredAllen:option_if_let_else, r=flip1995
Suggest `Option::map_or`(_else) for `if let Some { y } else { x }` Fixes #5203 There are two issues with this code that I have noticed: - Use of `Option::map_or` causes it to always evaluate the code in the else block. If that block is computationally expensive or if it updates some state (such as getting the next value from an iterator), then this change would cause the code to behave differently. In either of those circumstances, it should suggest Option::map_or_else, which takes both cases as a closure and runs one. However, I don't know how to check if the expression would change some state, so I left the lint's applicability as MaybeIncorrect. - There are lints which can trigger on specific sub-cases of this lint (`if_let_some_result`, `question_mark`, and `while_let_loop`) and suggest different changes (usually better ones because they're more specific). Is this acceptable for clippy to give multiple suggestions, or should I have the code check if those other lints trigger and then not trigger this lint if they do? changelog: Add lint [`option_if_let_else`]
2 parents 57cdf2d + c8f700e commit ac85692

26 files changed

+760
-228
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,7 @@ Released 2018-09-13
15771577
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
15781578
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
15791579
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
1580+
[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
15801581
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
15811582
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
15821583
[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option

clippy_lints/src/attrs.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -481,15 +481,14 @@ fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
481481
}
482482

483483
fn is_relevant_block(cx: &LateContext<'_>, tables: &ty::TypeckTables<'_>, block: &Block<'_>) -> bool {
484-
if let Some(stmt) = block.stmts.first() {
485-
match &stmt.kind {
484+
block.stmts.first().map_or(
485+
block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e)),
486+
|stmt| match &stmt.kind {
486487
StmtKind::Local(_) => true,
487488
StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, tables, expr),
488489
_ => false,
489-
}
490-
} else {
491-
block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e))
492-
}
490+
},
491+
)
493492
}
494493

495494
fn is_relevant_expr(cx: &LateContext<'_>, tables: &ty::TypeckTables<'_>, expr: &Expr<'_>) -> bool {
@@ -499,11 +498,10 @@ fn is_relevant_expr(cx: &LateContext<'_>, tables: &ty::TypeckTables<'_>, expr: &
499498
ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
500499
ExprKind::Call(path_expr, _) => {
501500
if let ExprKind::Path(qpath) = &path_expr.kind {
502-
if let Some(fun_id) = tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
503-
!match_def_path(cx, fun_id, &paths::BEGIN_PANIC)
504-
} else {
505-
true
506-
}
501+
tables
502+
.qpath_res(qpath, path_expr.hir_id)
503+
.opt_def_id()
504+
.map_or(true, |fun_id| !match_def_path(cx, fun_id, &paths::BEGIN_PANIC))
507505
} else {
508506
true
509507
}

clippy_lints/src/if_let_mutex.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,10 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
135135
}
136136
}
137137

138-
impl<'tcx> ArmVisitor<'_, 'tcx> {
138+
impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
139139
fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool {
140-
if let Some(arm_mutex) = self.found_mutex {
141-
SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)
142-
} else {
143-
false
144-
}
140+
self.found_mutex
141+
.map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex))
145142
}
146143
}
147144

clippy_lints/src/len_zero.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -302,16 +302,12 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
302302

303303
let ty = &walk_ptrs_ty(cx.tables().expr_ty(expr));
304304
match ty.kind {
305-
ty::Dynamic(ref tt, ..) => {
306-
if let Some(principal) = tt.principal() {
307-
cx.tcx
308-
.associated_items(principal.def_id())
309-
.in_definition_order()
310-
.any(|item| is_is_empty(cx, &item))
311-
} else {
312-
false
313-
}
314-
},
305+
ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| {
306+
cx.tcx
307+
.associated_items(principal.def_id())
308+
.in_definition_order()
309+
.any(|item| is_is_empty(cx, &item))
310+
}),
315311
ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
316312
ty::Adt(id, _) => has_is_empty_impl(cx, id.did),
317313
ty::Array(..) | ty::Slice(..) | ty::Str => true,

clippy_lints/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ mod non_copy_const;
264264
mod non_expressive_names;
265265
mod open_options;
266266
mod option_env_unwrap;
267+
mod option_if_let_else;
267268
mod overflow_check_conditional;
268269
mod panic_unimplemented;
269270
mod partialeq_ne_impl;
@@ -734,6 +735,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
734735
&non_expressive_names::SIMILAR_NAMES,
735736
&open_options::NONSENSICAL_OPEN_OPTIONS,
736737
&option_env_unwrap::OPTION_ENV_UNWRAP,
738+
&option_if_let_else::OPTION_IF_LET_ELSE,
737739
&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
738740
&panic_unimplemented::PANIC,
739741
&panic_unimplemented::PANIC_PARAMS,
@@ -1052,6 +1054,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10521054
store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default());
10531055
store.register_late_pass(|| box unnamed_address::UnnamedAddress);
10541056
store.register_late_pass(|| box dereference::Dereferencing);
1057+
store.register_late_pass(|| box option_if_let_else::OptionIfLetElse);
10551058
store.register_late_pass(|| box future_not_send::FutureNotSend);
10561059
store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
10571060
store.register_late_pass(|| box if_let_mutex::IfLetMutex);
@@ -1158,6 +1161,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11581161
LintId::of(&needless_continue::NEEDLESS_CONTINUE),
11591162
LintId::of(&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
11601163
LintId::of(&non_expressive_names::SIMILAR_NAMES),
1164+
LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE),
11611165
LintId::of(&ranges::RANGE_PLUS_ONE),
11621166
LintId::of(&shadow::SHADOW_UNRELATED),
11631167
LintId::of(&strings::STRING_ADD_ASSIGN),

clippy_lints/src/literal_representation.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,13 @@ impl LiteralDigitGrouping {
264264

265265
let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
266266
(exponent, &["32", "64"][..], 'f')
267-
} else if let Some(fraction) = &mut num_lit.fraction {
268-
(fraction, &["32", "64"][..], 'f')
269267
} else {
270-
(&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
268+
num_lit
269+
.fraction
270+
.as_mut()
271+
.map_or((&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i'), |fraction| {
272+
(fraction, &["32", "64"][..], 'f')
273+
})
271274
};
272275

273276
let mut split = part.rsplit('_');

clippy_lints/src/loops.rs

+9-23
Original file line numberDiff line numberDiff line change
@@ -686,13 +686,9 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
686686
NeverLoopResult::AlwaysBreak
687687
}
688688
},
689-
ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => {
690-
if let Some(ref e) = *e {
691-
combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
692-
} else {
693-
NeverLoopResult::AlwaysBreak
694-
}
695-
},
689+
ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
690+
combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
691+
}),
696692
ExprKind::InlineAsm(ref asm) => asm
697693
.operands
698694
.iter()
@@ -1881,13 +1877,9 @@ fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
18811877
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
18821878
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
18831879
match ty.kind {
1884-
ty::Array(_, n) => {
1885-
if let Some(val) = n.try_eval_usize(cx.tcx, cx.param_env) {
1886-
(0..=32).contains(&val)
1887-
} else {
1888-
false
1889-
}
1890-
},
1880+
ty::Array(_, n) => n
1881+
.try_eval_usize(cx.tcx, cx.param_env)
1882+
.map_or(false, |val| (0..=32).contains(&val)),
18911883
_ => false,
18921884
}
18931885
}
@@ -1899,11 +1891,7 @@ fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<
18991891
return None;
19001892
}
19011893
if let StmtKind::Local(ref local) = block.stmts[0].kind {
1902-
if let Some(expr) = local.init {
1903-
Some(expr)
1904-
} else {
1905-
None
1906-
}
1894+
local.init //.map(|expr| expr)
19071895
} else {
19081896
None
19091897
}
@@ -2023,15 +2011,13 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
20232011
if let PatKind::Binding(.., ident, _) = local.pat.kind {
20242012
self.name = Some(ident.name);
20252013

2026-
self.state = if let Some(ref init) = local.init {
2014+
self.state = local.init.as_ref().map_or(VarState::Declared, |init| {
20272015
if is_integer_const(&self.cx, init, 0) {
20282016
VarState::Warn
20292017
} else {
20302018
VarState::Declared
20312019
}
2032-
} else {
2033-
VarState::Declared
2034-
}
2020+
})
20352021
}
20362022
}
20372023
}

clippy_lints/src/methods/mod.rs

+3-7
Original file line numberDiff line numberDiff line change
@@ -2460,13 +2460,9 @@ fn derefs_to_slice<'tcx>(
24602460
ty::Slice(_) => true,
24612461
ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()),
24622462
ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym!(vec_type)),
2463-
ty::Array(_, size) => {
2464-
if let Some(size) = size.try_eval_usize(cx.tcx, cx.param_env) {
2465-
size < 32
2466-
} else {
2467-
false
2468-
}
2469-
},
2463+
ty::Array(_, size) => size
2464+
.try_eval_usize(cx.tcx, cx.param_env)
2465+
.map_or(false, |size| size < 32),
24702466
ty::Ref(_, inner, _) => may_slice(cx, inner),
24712467
_ => false,
24722468
}

clippy_lints/src/methods/unnecessary_filter_map.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,10 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
7777
}
7878
(true, true)
7979
},
80-
hir::ExprKind::Block(ref block, _) => {
81-
if let Some(expr) = &block.expr {
82-
check_expression(cx, arg_id, &expr)
83-
} else {
84-
(false, false)
85-
}
86-
},
80+
hir::ExprKind::Block(ref block, _) => block
81+
.expr
82+
.as_ref()
83+
.map_or((false, false), |expr| check_expression(cx, arg_id, &expr)),
8784
hir::ExprKind::Match(_, arms, _) => {
8885
let mut found_mapping = false;
8986
let mut found_filtering = false;

clippy_lints/src/minmax.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for MinMaxPass {
5353
}
5454
}
5555

56-
#[derive(PartialEq, Eq, Debug)]
56+
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
5757
enum MinMax {
5858
Min,
5959
Max,
@@ -86,16 +86,15 @@ fn fetch_const<'a>(cx: &LateContext<'_>, args: &'a [Expr<'a>], m: MinMax) -> Opt
8686
if args.len() != 2 {
8787
return None;
8888
}
89-
if let Some(c) = constant_simple(cx, cx.tables(), &args[0]) {
90-
if constant_simple(cx, cx.tables(), &args[1]).is_none() {
91-
// otherwise ignore
92-
Some((m, c, &args[1]))
93-
} else {
94-
None
95-
}
96-
} else if let Some(c) = constant_simple(cx, cx.tables(), &args[1]) {
97-
Some((m, c, &args[0]))
98-
} else {
99-
None
100-
}
89+
constant_simple(cx, cx.tables(), &args[0]).map_or_else(
90+
|| constant_simple(cx, cx.tables(), &args[1]).map(|c| (m, c, &args[0])),
91+
|c| {
92+
if constant_simple(cx, cx.tables(), &args[1]).is_none() {
93+
// otherwise ignore
94+
Some((m, c, &args[1]))
95+
} else {
96+
None
97+
}
98+
},
99+
)
101100
}

clippy_lints/src/misc.rs

+4-10
Original file line numberDiff line numberDiff line change
@@ -682,16 +682,10 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left:
682682
/// `unused_variables`'s idea
683683
/// of what it means for an expression to be "used".
684684
fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
685-
if let Some(parent) = get_parent_expr(cx, expr) {
686-
match parent.kind {
687-
ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => {
688-
SpanlessEq::new(cx).eq_expr(rhs, expr)
689-
},
690-
_ => is_used(cx, parent),
691-
}
692-
} else {
693-
true
694-
}
685+
get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
686+
ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
687+
_ => is_used(cx, parent),
688+
})
695689
}
696690

697691
/// Tests whether an expression is in a macro expansion (e.g., something

0 commit comments

Comments
 (0)