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`]
This commit is contained in:
commit
ac856922f8
@ -1577,6 +1577,7 @@ Released 2018-09-13
|
||||
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
|
||||
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
|
||||
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
|
||||
[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
|
||||
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
|
||||
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
|
||||
[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
|
||||
|
@ -481,15 +481,14 @@ fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
|
||||
}
|
||||
|
||||
fn is_relevant_block(cx: &LateContext<'_>, tables: &ty::TypeckTables<'_>, block: &Block<'_>) -> bool {
|
||||
if let Some(stmt) = block.stmts.first() {
|
||||
match &stmt.kind {
|
||||
block.stmts.first().map_or(
|
||||
block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e)),
|
||||
|stmt| match &stmt.kind {
|
||||
StmtKind::Local(_) => true,
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, tables, expr),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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: &
|
||||
ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
|
||||
ExprKind::Call(path_expr, _) => {
|
||||
if let ExprKind::Path(qpath) = &path_expr.kind {
|
||||
if let Some(fun_id) = tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
|
||||
!match_def_path(cx, fun_id, &paths::BEGIN_PANIC)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
tables
|
||||
.qpath_res(qpath, path_expr.hir_id)
|
||||
.opt_def_id()
|
||||
.map_or(true, |fun_id| !match_def_path(cx, fun_id, &paths::BEGIN_PANIC))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -135,13 +135,10 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ArmVisitor<'_, 'tcx> {
|
||||
impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
|
||||
fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool {
|
||||
if let Some(arm_mutex) = self.found_mutex {
|
||||
SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
self.found_mutex
|
||||
.map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,16 +302,12 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
|
||||
let ty = &walk_ptrs_ty(cx.tables().expr_ty(expr));
|
||||
match ty.kind {
|
||||
ty::Dynamic(ref tt, ..) => {
|
||||
if let Some(principal) = tt.principal() {
|
||||
cx.tcx
|
||||
.associated_items(principal.def_id())
|
||||
.in_definition_order()
|
||||
.any(|item| is_is_empty(cx, &item))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| {
|
||||
cx.tcx
|
||||
.associated_items(principal.def_id())
|
||||
.in_definition_order()
|
||||
.any(|item| is_is_empty(cx, &item))
|
||||
}),
|
||||
ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
|
||||
ty::Adt(id, _) => has_is_empty_impl(cx, id.did),
|
||||
ty::Array(..) | ty::Slice(..) | ty::Str => true,
|
||||
|
@ -264,6 +264,7 @@ mod non_copy_const;
|
||||
mod non_expressive_names;
|
||||
mod open_options;
|
||||
mod option_env_unwrap;
|
||||
mod option_if_let_else;
|
||||
mod overflow_check_conditional;
|
||||
mod panic_unimplemented;
|
||||
mod partialeq_ne_impl;
|
||||
@ -734,6 +735,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
&non_expressive_names::SIMILAR_NAMES,
|
||||
&open_options::NONSENSICAL_OPEN_OPTIONS,
|
||||
&option_env_unwrap::OPTION_ENV_UNWRAP,
|
||||
&option_if_let_else::OPTION_IF_LET_ELSE,
|
||||
&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
|
||||
&panic_unimplemented::PANIC,
|
||||
&panic_unimplemented::PANIC_PARAMS,
|
||||
@ -1052,6 +1054,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default());
|
||||
store.register_late_pass(|| box unnamed_address::UnnamedAddress);
|
||||
store.register_late_pass(|| box dereference::Dereferencing);
|
||||
store.register_late_pass(|| box option_if_let_else::OptionIfLetElse);
|
||||
store.register_late_pass(|| box future_not_send::FutureNotSend);
|
||||
store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
|
||||
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:
|
||||
LintId::of(&needless_continue::NEEDLESS_CONTINUE),
|
||||
LintId::of(&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
|
||||
LintId::of(&non_expressive_names::SIMILAR_NAMES),
|
||||
LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE),
|
||||
LintId::of(&ranges::RANGE_PLUS_ONE),
|
||||
LintId::of(&shadow::SHADOW_UNRELATED),
|
||||
LintId::of(&strings::STRING_ADD_ASSIGN),
|
||||
|
@ -264,10 +264,13 @@ impl LiteralDigitGrouping {
|
||||
|
||||
let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
|
||||
(exponent, &["32", "64"][..], 'f')
|
||||
} else if let Some(fraction) = &mut num_lit.fraction {
|
||||
(fraction, &["32", "64"][..], 'f')
|
||||
} else {
|
||||
(&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
|
||||
num_lit
|
||||
.fraction
|
||||
.as_mut()
|
||||
.map_or((&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i'), |fraction| {
|
||||
(fraction, &["32", "64"][..], 'f')
|
||||
})
|
||||
};
|
||||
|
||||
let mut split = part.rsplit('_');
|
||||
|
@ -686,13 +686,9 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
|
||||
NeverLoopResult::AlwaysBreak
|
||||
}
|
||||
},
|
||||
ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => {
|
||||
if let Some(ref e) = *e {
|
||||
combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
|
||||
} else {
|
||||
NeverLoopResult::AlwaysBreak
|
||||
}
|
||||
},
|
||||
ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
|
||||
combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
|
||||
}),
|
||||
ExprKind::InlineAsm(ref asm) => asm
|
||||
.operands
|
||||
.iter()
|
||||
@ -1881,13 +1877,9 @@ fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
|
||||
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
|
||||
match ty.kind {
|
||||
ty::Array(_, n) => {
|
||||
if let Some(val) = n.try_eval_usize(cx.tcx, cx.param_env) {
|
||||
(0..=32).contains(&val)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ty::Array(_, n) => n
|
||||
.try_eval_usize(cx.tcx, cx.param_env)
|
||||
.map_or(false, |val| (0..=32).contains(&val)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -1899,11 +1891,7 @@ fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<
|
||||
return None;
|
||||
}
|
||||
if let StmtKind::Local(ref local) = block.stmts[0].kind {
|
||||
if let Some(expr) = local.init {
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
local.init //.map(|expr| expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -2023,15 +2011,13 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
|
||||
if let PatKind::Binding(.., ident, _) = local.pat.kind {
|
||||
self.name = Some(ident.name);
|
||||
|
||||
self.state = if let Some(ref init) = local.init {
|
||||
self.state = local.init.as_ref().map_or(VarState::Declared, |init| {
|
||||
if is_integer_const(&self.cx, init, 0) {
|
||||
VarState::Warn
|
||||
} else {
|
||||
VarState::Declared
|
||||
}
|
||||
} else {
|
||||
VarState::Declared
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2460,13 +2460,9 @@ fn derefs_to_slice<'tcx>(
|
||||
ty::Slice(_) => true,
|
||||
ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()),
|
||||
ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym!(vec_type)),
|
||||
ty::Array(_, size) => {
|
||||
if let Some(size) = size.try_eval_usize(cx.tcx, cx.param_env) {
|
||||
size < 32
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ty::Array(_, size) => size
|
||||
.try_eval_usize(cx.tcx, cx.param_env)
|
||||
.map_or(false, |size| size < 32),
|
||||
ty::Ref(_, inner, _) => may_slice(cx, inner),
|
||||
_ => false,
|
||||
}
|
||||
|
@ -77,13 +77,10 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
|
||||
}
|
||||
(true, true)
|
||||
},
|
||||
hir::ExprKind::Block(ref block, _) => {
|
||||
if let Some(expr) = &block.expr {
|
||||
check_expression(cx, arg_id, &expr)
|
||||
} else {
|
||||
(false, false)
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Block(ref block, _) => block
|
||||
.expr
|
||||
.as_ref()
|
||||
.map_or((false, false), |expr| check_expression(cx, arg_id, &expr)),
|
||||
hir::ExprKind::Match(_, arms, _) => {
|
||||
let mut found_mapping = false;
|
||||
let mut found_filtering = false;
|
||||
|
@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for MinMaxPass {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
enum MinMax {
|
||||
Min,
|
||||
Max,
|
||||
@ -86,16 +86,15 @@ fn fetch_const<'a>(cx: &LateContext<'_>, args: &'a [Expr<'a>], m: MinMax) -> Opt
|
||||
if args.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
if let Some(c) = constant_simple(cx, cx.tables(), &args[0]) {
|
||||
if constant_simple(cx, cx.tables(), &args[1]).is_none() {
|
||||
// otherwise ignore
|
||||
Some((m, c, &args[1]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(c) = constant_simple(cx, cx.tables(), &args[1]) {
|
||||
Some((m, c, &args[0]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
constant_simple(cx, cx.tables(), &args[0]).map_or_else(
|
||||
|| constant_simple(cx, cx.tables(), &args[1]).map(|c| (m, c, &args[0])),
|
||||
|c| {
|
||||
if constant_simple(cx, cx.tables(), &args[1]).is_none() {
|
||||
// otherwise ignore
|
||||
Some((m, c, &args[1]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -682,16 +682,10 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left:
|
||||
/// `unused_variables`'s idea
|
||||
/// of what it means for an expression to be "used".
|
||||
fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
match parent.kind {
|
||||
ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => {
|
||||
SpanlessEq::new(cx).eq_expr(rhs, expr)
|
||||
},
|
||||
_ => is_used(cx, parent),
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
|
||||
ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
|
||||
_ => is_used(cx, parent),
|
||||
})
|
||||
}
|
||||
|
||||
/// Tests whether an expression is in a macro expansion (e.g., something
|
||||
|
267
clippy_lints/src/option_if_let_else.rs
Normal file
267
clippy_lints/src/option_if_let_else.rs
Normal file
@ -0,0 +1,267 @@
|
||||
use crate::utils;
|
||||
use crate::utils::sugg::Sugg;
|
||||
use crate::utils::{match_type, paths, span_lint_and_sugg};
|
||||
use if_chain::if_chain;
|
||||
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, PatKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:**
|
||||
/// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
|
||||
/// idiomatically done with `Option::map_or` (if the else bit is a simple
|
||||
/// expression) or `Option::map_or_else` (if the else bit is a longer
|
||||
/// block).
|
||||
///
|
||||
/// **Why is this bad?**
|
||||
/// Using the dedicated functions of the Option type is clearer and
|
||||
/// more concise than an if let expression.
|
||||
///
|
||||
/// **Known problems:**
|
||||
/// This lint uses whether the block is just an expression or if it has
|
||||
/// more statements to decide whether to use `Option::map_or` or
|
||||
/// `Option::map_or_else`. If you have a single expression which calls
|
||||
/// an expensive function, then it would be more efficient to use
|
||||
/// `Option::map_or_else`, but this lint would suggest `Option::map_or`.
|
||||
///
|
||||
/// Also, this lint uses a deliberately conservative metric for checking
|
||||
/// if the inside of either body contains breaks or continues which will
|
||||
/// cause it to not suggest a fix if either block contains a loop with
|
||||
/// continues or breaks contained within the loop.
|
||||
///
|
||||
/// **Example:**
|
||||
///
|
||||
/// ```rust
|
||||
/// # let optional: Option<u32> = Some(0);
|
||||
/// # fn do_complicated_function() -> u32 { 5 };
|
||||
/// let _ = if let Some(foo) = optional {
|
||||
/// foo
|
||||
/// } else {
|
||||
/// 5
|
||||
/// };
|
||||
/// let _ = if let Some(foo) = optional {
|
||||
/// foo
|
||||
/// } else {
|
||||
/// let y = do_complicated_function();
|
||||
/// y*y
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// should be
|
||||
///
|
||||
/// ```rust
|
||||
/// # let optional: Option<u32> = Some(0);
|
||||
/// # fn do_complicated_function() -> u32 { 5 };
|
||||
/// let _ = optional.map_or(5, |foo| foo);
|
||||
/// let _ = optional.map_or_else(||{
|
||||
/// let y = do_complicated_function();
|
||||
/// y*y
|
||||
/// }, |foo| foo);
|
||||
/// ```
|
||||
pub OPTION_IF_LET_ELSE,
|
||||
pedantic,
|
||||
"reimplementation of Option::map_or"
|
||||
}
|
||||
|
||||
declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]);
|
||||
|
||||
/// Returns true iff the given expression is the result of calling `Result::ok`
|
||||
fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
|
||||
if let ExprKind::MethodCall(ref path, _, &[ref receiver], _) = &expr.kind {
|
||||
path.ident.name.to_ident_string() == "ok" && match_type(cx, &cx.tables().expr_ty(&receiver), &paths::RESULT)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct containing information about occurences of the
|
||||
/// `if let Some(..) = .. else` construct that this lint detects.
|
||||
struct OptionIfLetElseOccurence {
|
||||
option: String,
|
||||
method_sugg: String,
|
||||
some_expr: String,
|
||||
none_expr: String,
|
||||
wrap_braces: bool,
|
||||
}
|
||||
|
||||
struct ReturnBreakContinueMacroVisitor {
|
||||
seen_return_break_continue: bool,
|
||||
}
|
||||
impl ReturnBreakContinueMacroVisitor {
|
||||
fn new() -> ReturnBreakContinueMacroVisitor {
|
||||
ReturnBreakContinueMacroVisitor {
|
||||
seen_return_break_continue: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
|
||||
type Map = Map<'tcx>;
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
|
||||
if self.seen_return_break_continue {
|
||||
// No need to look farther if we've already seen one of them
|
||||
return;
|
||||
}
|
||||
match &ex.kind {
|
||||
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
|
||||
self.seen_return_break_continue = true;
|
||||
},
|
||||
// Something special could be done here to handle while or for loop
|
||||
// desugaring, as this will detect a break if there's a while loop
|
||||
// or a for loop inside the expression.
|
||||
_ => {
|
||||
if utils::in_macro(ex.span) {
|
||||
self.seen_return_break_continue = true;
|
||||
} else {
|
||||
rustc_hir::intravisit::walk_expr(self, ex);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
|
||||
let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
|
||||
recursive_visitor.visit_expr(expression);
|
||||
recursive_visitor.seen_return_break_continue
|
||||
}
|
||||
|
||||
/// Extracts the body of a given arm. If the arm contains only an expression,
|
||||
/// then it returns the expression. Otherwise, it returns the entire block
|
||||
fn extract_body_from_arm<'a>(arm: &'a Arm<'a>) -> Option<&'a Expr<'a>> {
|
||||
if let ExprKind::Block(
|
||||
Block {
|
||||
stmts: statements,
|
||||
expr: Some(expr),
|
||||
..
|
||||
},
|
||||
_,
|
||||
) = &arm.body.kind
|
||||
{
|
||||
if let [] = statements {
|
||||
Some(&expr)
|
||||
} else {
|
||||
Some(&arm.body)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is the else body of an if/else expression, then we need to wrap
|
||||
/// it in curcly braces. Otherwise, we don't.
|
||||
fn should_wrap_in_braces(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
utils::get_enclosing_block(cx, expr.hir_id).map_or(false, |parent| {
|
||||
if let Some(Expr {
|
||||
kind:
|
||||
ExprKind::Match(
|
||||
_,
|
||||
arms,
|
||||
MatchSource::IfDesugar {
|
||||
contains_else_clause: true,
|
||||
}
|
||||
| MatchSource::IfLetDesugar {
|
||||
contains_else_clause: true,
|
||||
},
|
||||
),
|
||||
..
|
||||
}) = parent.expr
|
||||
{
|
||||
expr.hir_id == arms[1].body.hir_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String {
|
||||
format!(
|
||||
"{}{}",
|
||||
Sugg::hir(cx, cond_expr, "..").maybe_par(),
|
||||
if as_mut {
|
||||
".as_mut()"
|
||||
} else if as_ref {
|
||||
".as_ref()"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// If this expression is the option if let/else construct we're detecting, then
|
||||
/// this function returns an `OptionIfLetElseOccurence` struct with details if
|
||||
/// this construct is found, or None if this construct is not found.
|
||||
fn detect_option_if_let_else(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OptionIfLetElseOccurence> {
|
||||
if_chain! {
|
||||
if !utils::in_macro(expr.span); // Don't lint macros, because it behaves weirdly
|
||||
if let ExprKind::Match(cond_expr, arms, MatchSource::IfLetDesugar{contains_else_clause: true}) = &expr.kind;
|
||||
if arms.len() == 2;
|
||||
if !is_result_ok(cx, cond_expr); // Don't lint on Result::ok because a different lint does it already
|
||||
if let PatKind::TupleStruct(struct_qpath, &[inner_pat], _) = &arms[0].pat.kind;
|
||||
if utils::match_qpath(struct_qpath, &paths::OPTION_SOME);
|
||||
if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind;
|
||||
if !contains_return_break_continue_macro(arms[0].body);
|
||||
if !contains_return_break_continue_macro(arms[1].body);
|
||||
then {
|
||||
let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" };
|
||||
let some_body = extract_body_from_arm(&arms[0])?;
|
||||
let none_body = extract_body_from_arm(&arms[1])?;
|
||||
let method_sugg = match &none_body.kind {
|
||||
ExprKind::Block(..) => "map_or_else",
|
||||
_ => "map_or",
|
||||
};
|
||||
let capture_name = id.name.to_ident_string();
|
||||
let wrap_braces = should_wrap_in_braces(cx, expr);
|
||||
let (as_ref, as_mut) = match &cond_expr.kind {
|
||||
ExprKind::AddrOf(_, Mutability::Not, _) => (true, false),
|
||||
ExprKind::AddrOf(_, Mutability::Mut, _) => (false, true),
|
||||
_ => (bind_annotation == &BindingAnnotation::Ref, bind_annotation == &BindingAnnotation::RefMut),
|
||||
};
|
||||
let cond_expr = match &cond_expr.kind {
|
||||
// Pointer dereferencing happens automatically, so we can omit it in the suggestion
|
||||
ExprKind::Unary(UnOp::UnDeref, expr) | ExprKind::AddrOf(_, _, expr) => expr,
|
||||
_ => cond_expr,
|
||||
};
|
||||
Some(OptionIfLetElseOccurence {
|
||||
option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
|
||||
method_sugg: method_sugg.to_string(),
|
||||
some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir(cx, some_body, "..")),
|
||||
none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir(cx, none_body, "..")),
|
||||
wrap_braces,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LateLintPass<'a> for OptionIfLetElse {
|
||||
fn check_expr(&mut self, cx: &LateContext<'a>, expr: &Expr<'_>) {
|
||||
if let Some(detection) = detect_option_if_let_else(cx, expr) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OPTION_IF_LET_ELSE,
|
||||
expr.span,
|
||||
format!("use Option::{} instead of an if let/else", detection.method_sugg).as_str(),
|
||||
"try",
|
||||
format!(
|
||||
"{}{}.{}({}, {}){}",
|
||||
if detection.wrap_braces { "{ " } else { "" },
|
||||
detection.option,
|
||||
detection.method_sugg,
|
||||
detection.none_expr,
|
||||
detection.some_expr,
|
||||
if detection.wrap_braces { " }" } else { "" },
|
||||
),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -259,15 +259,15 @@ fn is_unit_expr(expr: &ast::Expr) -> bool {
|
||||
|
||||
fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
|
||||
let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) {
|
||||
if let Some(rpos) = fn_source.rfind("->") {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
(
|
||||
ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
} else {
|
||||
(ty.span, Applicability::MaybeIncorrect)
|
||||
}
|
||||
fn_source
|
||||
.rfind("->")
|
||||
.map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
|
||||
(
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
(ty.span, Applicability::MaybeIncorrect)
|
||||
};
|
||||
|
@ -165,14 +165,10 @@ fn check_local<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>, bindings: &
|
||||
|
||||
fn is_binding(cx: &LateContext<'_>, pat_id: HirId) -> bool {
|
||||
let var_ty = cx.tables().node_type_opt(pat_id);
|
||||
if let Some(var_ty) = var_ty {
|
||||
match var_ty.kind {
|
||||
ty::Adt(..) => false,
|
||||
_ => true,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
var_ty.map_or(false, |var_ty| match var_ty.kind {
|
||||
ty::Adt(..) => false,
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_pat<'tcx>(
|
||||
|
@ -1205,16 +1205,19 @@ fn span_lossless_lint(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast
|
||||
// has parens on the outside, they are no longer needed.
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let opt = snippet_opt(cx, op.span);
|
||||
let sugg = if let Some(ref snip) = opt {
|
||||
if should_strip_parens(op, snip) {
|
||||
&snip[1..snip.len() - 1]
|
||||
} else {
|
||||
snip.as_str()
|
||||
}
|
||||
} else {
|
||||
applicability = Applicability::HasPlaceholders;
|
||||
".."
|
||||
};
|
||||
let sugg = opt.as_ref().map_or_else(
|
||||
|| {
|
||||
applicability = Applicability::HasPlaceholders;
|
||||
".."
|
||||
},
|
||||
|snip| {
|
||||
if should_strip_parens(op, snip) {
|
||||
&snip[1..snip.len() - 1]
|
||||
} else {
|
||||
snip.as_str()
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -167,14 +167,13 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
|
||||
if let TyKind::Path(QPath::Resolved(_, ref item_path)) = item_type.kind;
|
||||
then {
|
||||
let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
|
||||
let should_check = if let Some(ref params) = *parameters {
|
||||
!params.parenthesized && !params.args.iter().any(|arg| match arg {
|
||||
let should_check = parameters.as_ref().map_or(
|
||||
true,
|
||||
|params| !params.parenthesized && !params.args.iter().any(|arg| match arg {
|
||||
GenericArg::Lifetime(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
} else {
|
||||
true
|
||||
};
|
||||
);
|
||||
|
||||
if should_check {
|
||||
let visitor = &mut UseSelfVisitor {
|
||||
|
@ -65,42 +65,45 @@ pub fn get_attr<'a>(
|
||||
};
|
||||
let attr_segments = &attr.path.segments;
|
||||
if attr_segments.len() == 2 && attr_segments[0].ident.to_string() == "clippy" {
|
||||
if let Some(deprecation_status) =
|
||||
BUILTIN_ATTRIBUTES
|
||||
.iter()
|
||||
.find_map(|(builtin_name, deprecation_status)| {
|
||||
if *builtin_name == attr_segments[1].ident.to_string() {
|
||||
Some(deprecation_status)
|
||||
} else {
|
||||
None
|
||||
BUILTIN_ATTRIBUTES
|
||||
.iter()
|
||||
.find_map(|(builtin_name, deprecation_status)| {
|
||||
if *builtin_name == attr_segments[1].ident.to_string() {
|
||||
Some(deprecation_status)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map_or_else(
|
||||
|| {
|
||||
sess.span_err(attr_segments[1].ident.span, "Usage of unknown attribute");
|
||||
false
|
||||
},
|
||||
|deprecation_status| {
|
||||
let mut diag =
|
||||
sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute");
|
||||
match *deprecation_status {
|
||||
DeprecationStatus::Deprecated => {
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::Replaced(new_name) => {
|
||||
diag.span_suggestion(
|
||||
attr_segments[1].ident.span,
|
||||
"consider using",
|
||||
new_name.to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::None => {
|
||||
diag.cancel();
|
||||
attr_segments[1].ident.to_string() == name
|
||||
},
|
||||
}
|
||||
})
|
||||
{
|
||||
let mut diag = sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute");
|
||||
match *deprecation_status {
|
||||
DeprecationStatus::Deprecated => {
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::Replaced(new_name) => {
|
||||
diag.span_suggestion(
|
||||
attr_segments[1].ident.span,
|
||||
"consider using",
|
||||
new_name.to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::None => {
|
||||
diag.cancel();
|
||||
attr_segments[1].ident.to_string() == name
|
||||
},
|
||||
}
|
||||
} else {
|
||||
sess.span_err(attr_segments[1].ident.span, "Usage of unknown attribute");
|
||||
false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -153,11 +153,7 @@ pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symb
|
||||
pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
|
||||
let def_id = cx.tables().type_dependent_def_id(expr.hir_id).unwrap();
|
||||
let trt_id = cx.tcx.trait_of_item(def_id);
|
||||
if let Some(trt_id) = trt_id {
|
||||
match_def_path(cx, trt_id, path)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
|
||||
}
|
||||
|
||||
/// Checks if an expression references a variable of the given name.
|
||||
@ -600,21 +596,15 @@ pub fn snippet_block_with_applicability<'a, T: LintContext>(
|
||||
/// // ^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
|
||||
if let Some(first_char_pos) = first_char_in_first_line(cx, span) {
|
||||
span.with_lo(first_char_pos)
|
||||
} else {
|
||||
span
|
||||
}
|
||||
first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
|
||||
}
|
||||
|
||||
fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
|
||||
let line_span = line_span(cx, span);
|
||||
if let Some(snip) = snippet_opt(cx, line_span) {
|
||||
snippet_opt(cx, line_span).and_then(|snip| {
|
||||
snip.find(|c: char| !c.is_whitespace())
|
||||
.map(|pos| line_span.lo() + BytePos::from_usize(pos))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the indentation of the line of a span
|
||||
@ -626,11 +616,7 @@ fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePo
|
||||
/// // ^^ -- will return 4
|
||||
/// ```
|
||||
pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
|
||||
if let Some(snip) = snippet_opt(cx, line_span(cx, span)) {
|
||||
snip.find(|c: char| !c.is_whitespace())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
|
||||
}
|
||||
|
||||
/// Extends the span to the beginning of the spans line, incl. whitespaces.
|
||||
@ -738,25 +724,21 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
|
||||
let enclosing_node = map
|
||||
.get_enclosing_scope(hir_id)
|
||||
.and_then(|enclosing_id| map.find(enclosing_id));
|
||||
if let Some(node) = enclosing_node {
|
||||
match node {
|
||||
Node::Block(block) => Some(block),
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Fn(_, _, eid),
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(_, eid),
|
||||
..
|
||||
}) => match cx.tcx.hir().body(eid).value.kind {
|
||||
ExprKind::Block(ref block, _) => Some(block),
|
||||
_ => None,
|
||||
},
|
||||
enclosing_node.and_then(|node| match node {
|
||||
Node::Block(block) => Some(block),
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Fn(_, _, eid),
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(_, eid),
|
||||
..
|
||||
}) => match cx.tcx.hir().body(eid).value.kind {
|
||||
ExprKind::Block(ref block, _) => Some(block),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the base type for HIR references and pointers.
|
||||
@ -1328,11 +1310,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(did) = did {
|
||||
must_use_attr(&cx.tcx.get_attrs(did)).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
|
||||
}
|
||||
|
||||
pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
|
||||
|
@ -200,12 +200,10 @@ impl<'a> NumericLiteral<'a> {
|
||||
|
||||
fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
|
||||
debug_assert!(lit_kind.is_numeric());
|
||||
if let Some(suffix_length) = lit_suffix_length(lit_kind) {
|
||||
lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
|
||||
let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
|
||||
(unsuffixed, Some(suffix))
|
||||
} else {
|
||||
(src, None)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
|
||||
|
@ -492,20 +492,20 @@ fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
|
||||
/// before it on its line.
|
||||
fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
|
||||
let lo = cx.sess().source_map().lookup_char_pos(span.lo());
|
||||
if let Some(line) = lo.file.get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */) {
|
||||
if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
|
||||
// We can mix char and byte positions here because we only consider `[ \t]`.
|
||||
if lo.col == CharPos(pos) {
|
||||
Some(line[..pos].into())
|
||||
lo.file
|
||||
.get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
|
||||
.and_then(|line| {
|
||||
if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
|
||||
// We can mix char and byte positions here because we only consider `[ \t]`.
|
||||
if lo.col == CharPos(pos) {
|
||||
Some(line[..pos].into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience extension trait for `DiagnosticBuilder`.
|
||||
|
@ -297,12 +297,13 @@ impl EarlyLintPass for Write {
|
||||
if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
|
||||
if fmt_str.symbol == Symbol::intern("") {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let suggestion = if let Some(e) = expr {
|
||||
snippet_with_applicability(cx, e.span, "v", &mut applicability)
|
||||
} else {
|
||||
applicability = Applicability::HasPlaceholders;
|
||||
Cow::Borrowed("v")
|
||||
};
|
||||
let suggestion = expr.map_or_else(
|
||||
|| {
|
||||
applicability = Applicability::HasPlaceholders;
|
||||
Cow::Borrowed("v")
|
||||
},
|
||||
|e| snippet_with_applicability(cx, e.span, "v", &mut Applicability::MachineApplicable),
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -1620,6 +1620,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
|
||||
deprecation: None,
|
||||
module: "option_env_unwrap",
|
||||
},
|
||||
Lint {
|
||||
name: "option_if_let_else",
|
||||
group: "pedantic",
|
||||
desc: "reimplementation of Option::map_or",
|
||||
deprecation: None,
|
||||
module: "option_if_let_else",
|
||||
},
|
||||
Lint {
|
||||
name: "option_map_or_none",
|
||||
group: "style",
|
||||
|
@ -12,19 +12,11 @@ use std::path::{Path, PathBuf};
|
||||
mod cargo;
|
||||
|
||||
fn host_lib() -> PathBuf {
|
||||
if let Some(path) = option_env!("HOST_LIBS") {
|
||||
PathBuf::from(path)
|
||||
} else {
|
||||
cargo::CARGO_TARGET_DIR.join(env!("PROFILE"))
|
||||
}
|
||||
option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
|
||||
}
|
||||
|
||||
fn clippy_driver_path() -> PathBuf {
|
||||
if let Some(path) = option_env!("CLIPPY_DRIVER_PATH") {
|
||||
PathBuf::from(path)
|
||||
} else {
|
||||
cargo::TARGET_LIB.join("clippy-driver")
|
||||
}
|
||||
option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from)
|
||||
}
|
||||
|
||||
// When we'll want to use `extern crate ..` for a dependency that is used
|
||||
|
74
tests/ui/option_if_let_else.fixed
Normal file
74
tests/ui/option_if_let_else.fixed
Normal file
@ -0,0 +1,74 @@
|
||||
// run-rustfix
|
||||
#![warn(clippy::option_if_let_else)]
|
||||
|
||||
fn bad1(string: Option<&str>) -> (bool, &str) {
|
||||
string.map_or((false, "hello"), |x| (true, x))
|
||||
}
|
||||
|
||||
fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> {
|
||||
if string.is_none() {
|
||||
None
|
||||
} else { string.map_or(Some((false, "")), |x| Some((true, x))) }
|
||||
}
|
||||
|
||||
fn unop_bad(string: &Option<&str>, mut num: Option<i32>) {
|
||||
let _ = string.map_or(0, |s| s.len());
|
||||
let _ = num.as_ref().map_or(&0, |s| s);
|
||||
let _ = num.as_mut().map_or(&mut 0, |s| {
|
||||
*s += 1;
|
||||
s
|
||||
});
|
||||
let _ = num.as_ref().map_or(&0, |s| s);
|
||||
let _ = num.map_or(0, |mut s| {
|
||||
s += 1;
|
||||
s
|
||||
});
|
||||
let _ = num.as_mut().map_or(&mut 0, |s| {
|
||||
*s += 1;
|
||||
s
|
||||
});
|
||||
}
|
||||
|
||||
fn longer_body(arg: Option<u32>) -> u32 {
|
||||
arg.map_or(13, |x| {
|
||||
let y = x * x;
|
||||
y * y
|
||||
})
|
||||
}
|
||||
|
||||
fn test_map_or_else(arg: Option<u32>) {
|
||||
let _ = arg.map_or_else(|| {
|
||||
let mut y = 1;
|
||||
y = (y + 2 / y) / 2;
|
||||
y = (y + 2 / y) / 2;
|
||||
y
|
||||
}, |x| x * x * x * x);
|
||||
}
|
||||
|
||||
fn negative_tests(arg: Option<u32>) -> u32 {
|
||||
let _ = if let Some(13) = arg { "unlucky" } else { "lucky" };
|
||||
for _ in 0..10 {
|
||||
let _ = if let Some(x) = arg {
|
||||
x
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
let _ = if let Some(x) = arg {
|
||||
return x;
|
||||
} else {
|
||||
5
|
||||
};
|
||||
7
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let optional = Some(5);
|
||||
let _ = optional.map_or(5, |x| x + 2);
|
||||
let _ = bad1(None);
|
||||
let _ = else_if_option(None);
|
||||
unop_bad(&None, None);
|
||||
let _ = longer_body(None);
|
||||
test_map_or_else(None);
|
||||
let _ = negative_tests(None);
|
||||
}
|
92
tests/ui/option_if_let_else.rs
Normal file
92
tests/ui/option_if_let_else.rs
Normal file
@ -0,0 +1,92 @@
|
||||
// run-rustfix
|
||||
#![warn(clippy::option_if_let_else)]
|
||||
|
||||
fn bad1(string: Option<&str>) -> (bool, &str) {
|
||||
if let Some(x) = string {
|
||||
(true, x)
|
||||
} else {
|
||||
(false, "hello")
|
||||
}
|
||||
}
|
||||
|
||||
fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> {
|
||||
if string.is_none() {
|
||||
None
|
||||
} else if let Some(x) = string {
|
||||
Some((true, x))
|
||||
} else {
|
||||
Some((false, ""))
|
||||
}
|
||||
}
|
||||
|
||||
fn unop_bad(string: &Option<&str>, mut num: Option<i32>) {
|
||||
let _ = if let Some(s) = *string { s.len() } else { 0 };
|
||||
let _ = if let Some(s) = &num { s } else { &0 };
|
||||
let _ = if let Some(s) = &mut num {
|
||||
*s += 1;
|
||||
s
|
||||
} else {
|
||||
&mut 0
|
||||
};
|
||||
let _ = if let Some(ref s) = num { s } else { &0 };
|
||||
let _ = if let Some(mut s) = num {
|
||||
s += 1;
|
||||
s
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let _ = if let Some(ref mut s) = num {
|
||||
*s += 1;
|
||||
s
|
||||
} else {
|
||||
&mut 0
|
||||
};
|
||||
}
|
||||
|
||||
fn longer_body(arg: Option<u32>) -> u32 {
|
||||
if let Some(x) = arg {
|
||||
let y = x * x;
|
||||
y * y
|
||||
} else {
|
||||
13
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_or_else(arg: Option<u32>) {
|
||||
let _ = if let Some(x) = arg {
|
||||
x * x * x * x
|
||||
} else {
|
||||
let mut y = 1;
|
||||
y = (y + 2 / y) / 2;
|
||||
y = (y + 2 / y) / 2;
|
||||
y
|
||||
};
|
||||
}
|
||||
|
||||
fn negative_tests(arg: Option<u32>) -> u32 {
|
||||
let _ = if let Some(13) = arg { "unlucky" } else { "lucky" };
|
||||
for _ in 0..10 {
|
||||
let _ = if let Some(x) = arg {
|
||||
x
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
let _ = if let Some(x) = arg {
|
||||
return x;
|
||||
} else {
|
||||
5
|
||||
};
|
||||
7
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let optional = Some(5);
|
||||
let _ = if let Some(x) = optional { x + 2 } else { 5 };
|
||||
let _ = bad1(None);
|
||||
let _ = else_if_option(None);
|
||||
unop_bad(&None, None);
|
||||
let _ = longer_body(None);
|
||||
test_map_or_else(None);
|
||||
let _ = negative_tests(None);
|
||||
}
|
151
tests/ui/option_if_let_else.stderr
Normal file
151
tests/ui/option_if_let_else.stderr
Normal file
@ -0,0 +1,151 @@
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:5:5
|
||||
|
|
||||
LL | / if let Some(x) = string {
|
||||
LL | | (true, x)
|
||||
LL | | } else {
|
||||
LL | | (false, "hello")
|
||||
LL | | }
|
||||
| |_____^ help: try: `string.map_or((false, "hello"), |x| (true, x))`
|
||||
|
|
||||
= note: `-D clippy::option-if-let-else` implied by `-D warnings`
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:15:12
|
||||
|
|
||||
LL | } else if let Some(x) = string {
|
||||
| ____________^
|
||||
LL | | Some((true, x))
|
||||
LL | | } else {
|
||||
LL | | Some((false, ""))
|
||||
LL | | }
|
||||
| |_____^ help: try: `{ string.map_or(Some((false, "")), |x| Some((true, x))) }`
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:23:13
|
||||
|
|
||||
LL | let _ = if let Some(s) = *string { s.len() } else { 0 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())`
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:24:13
|
||||
|
|
||||
LL | let _ = if let Some(s) = &num { s } else { &0 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)`
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:25:13
|
||||
|
|
||||
LL | let _ = if let Some(s) = &mut num {
|
||||
| _____________^
|
||||
LL | | *s += 1;
|
||||
LL | | s
|
||||
LL | | } else {
|
||||
LL | | &mut 0
|
||||
LL | | };
|
||||
| |_____^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL | let _ = num.as_mut().map_or(&mut 0, |s| {
|
||||
LL | *s += 1;
|
||||
LL | s
|
||||
LL | });
|
||||
|
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:31:13
|
||||
|
|
||||
LL | let _ = if let Some(ref s) = num { s } else { &0 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)`
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:32:13
|
||||
|
|
||||
LL | let _ = if let Some(mut s) = num {
|
||||
| _____________^
|
||||
LL | | s += 1;
|
||||
LL | | s
|
||||
LL | | } else {
|
||||
LL | | 0
|
||||
LL | | };
|
||||
| |_____^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL | let _ = num.map_or(0, |mut s| {
|
||||
LL | s += 1;
|
||||
LL | s
|
||||
LL | });
|
||||
|
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:38:13
|
||||
|
|
||||
LL | let _ = if let Some(ref mut s) = num {
|
||||
| _____________^
|
||||
LL | | *s += 1;
|
||||
LL | | s
|
||||
LL | | } else {
|
||||
LL | | &mut 0
|
||||
LL | | };
|
||||
| |_____^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL | let _ = num.as_mut().map_or(&mut 0, |s| {
|
||||
LL | *s += 1;
|
||||
LL | s
|
||||
LL | });
|
||||
|
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:47:5
|
||||
|
|
||||
LL | / if let Some(x) = arg {
|
||||
LL | | let y = x * x;
|
||||
LL | | y * y
|
||||
LL | | } else {
|
||||
LL | | 13
|
||||
LL | | }
|
||||
| |_____^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL | arg.map_or(13, |x| {
|
||||
LL | let y = x * x;
|
||||
LL | y * y
|
||||
LL | })
|
||||
|
|
||||
|
||||
error: use Option::map_or_else instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:56:13
|
||||
|
|
||||
LL | let _ = if let Some(x) = arg {
|
||||
| _____________^
|
||||
LL | | x * x * x * x
|
||||
LL | | } else {
|
||||
LL | | let mut y = 1;
|
||||
... |
|
||||
LL | | y
|
||||
LL | | };
|
||||
| |_____^
|
||||
|
|
||||
help: try
|
||||
|
|
||||
LL | let _ = arg.map_or_else(|| {
|
||||
LL | let mut y = 1;
|
||||
LL | y = (y + 2 / y) / 2;
|
||||
LL | y = (y + 2 / y) / 2;
|
||||
LL | y
|
||||
LL | }, |x| x * x * x * x);
|
||||
|
|
||||
|
||||
error: use Option::map_or instead of an if let/else
|
||||
--> $DIR/option_if_let_else.rs:85:13
|
||||
|
|
||||
LL | let _ = if let Some(x) = optional { x + 2 } else { 5 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)`
|
||||
|
||||
error: aborting due to 11 previous errors
|
||||
|
Loading…
Reference in New Issue
Block a user