diff options
| author | J-ZhengLi <lizheng135@huawei.com> | 2022-04-16 16:57:06 +0800 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2022-08-21 10:24:27 +0200 |
| commit | 5d403c0b859259499886d3ee3d4b4b4dfaab6ae2 (patch) | |
| tree | abc2f1df49d17ffaabb7e776e164b2438b720a38 | |
| parent | 41309df8efe99848a79782ae98ad73638668381c (diff) | |
| download | rust-5d403c0b859259499886d3ee3d4b4b4dfaab6ae2.tar.gz rust-5d403c0b859259499886d3ee3d4b4b4dfaab6ae2.zip | |
allow check for `match` in lint [`option_if_let_else`]
and add test case for `Result`
| -rw-r--r-- | clippy_lints/src/option_if_let_else.rs | 170 | ||||
| -rw-r--r-- | tests/ui/option_if_let_else.fixed | 15 | ||||
| -rw-r--r-- | tests/ui/option_if_let_else.rs | 21 | ||||
| -rw-r--r-- | tests/ui/option_if_let_else.stderr | 22 |
4 files changed, 183 insertions, 45 deletions
diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 44f153cffac..b99966c5a5d 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -7,15 +7,18 @@ use clippy_utils::{ }; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::LangItem::OptionSome; -use rustc_hir::{def::Res, BindingAnnotation, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp}; +use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::{ + def::Res, Arm, BindingAnnotation, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath, UnOp, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more + /// Lints usage of `if let Some(v) = ... { y } else { x }` and + /// `match .. { Some(v) => y, None/_ => x }` which are more /// idiomatically done with `Option::map_or` (if the else bit is a pure /// expression) or `Option::map_or_else` (if the else bit is an impure /// expression). @@ -39,6 +42,10 @@ declare_clippy_lint! { /// } else { /// 5 /// }; + /// let _ = match optional { + /// Some(val) => val + 1, + /// None => 5 + /// }; /// let _ = if let Some(foo) = optional { /// foo /// } else { @@ -53,6 +60,7 @@ declare_clippy_lint! { /// # let optional: Option<u32> = Some(0); /// # fn do_complicated_function() -> u32 { 5 }; /// let _ = optional.map_or(5, |foo| foo); + /// let _ = optional.map_or(5, |val| val + 1); /// let _ = optional.map_or_else(||{ /// let y = do_complicated_function(); /// y*y @@ -76,9 +84,21 @@ fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { } } -/// A struct containing information about occurrences of the -/// `if let Some(..) = .. else` construct that this lint detects. -struct OptionIfLetElseOccurrence { +/// A struct containing information about occurrences of construct that this lint detects +/// +/// Such as: +/// +/// ```ignore +/// if let Some(..) = {..} else {..} +/// ``` +/// or +/// ```ignore +/// match x { +/// Some(..) => {..}, +/// None/_ => {..} +/// } +/// ``` +struct OptionOccurence { option: String, method_sugg: String, some_expr: String, @@ -99,43 +119,39 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo ) } -/// If this expression is the option if let/else construct we're detecting, then -/// this function returns an `OptionIfLetElseOccurrence` struct with details if -/// this construct is found, or None if this construct is not found. -fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurrence> { +fn try_get_option_occurence<'tcx>( + cx: &LateContext<'tcx>, + pat: &Pat<'tcx>, + expr: &Expr<'_>, + if_then: &'tcx Expr<'_>, + if_else: &'tcx Expr<'_>, +) -> Option<OptionOccurence> { + let cond_expr = match expr.kind { + ExprKind::Unary(UnOp::Deref, inner_expr) | ExprKind::AddrOf(_, _, inner_expr) => inner_expr, + _ => expr, + }; if_chain! { - if !expr.span.from_expansion(); // Don't lint macros, because it behaves weirdly - if !in_constant(cx, expr.hir_id); - if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) - = higher::IfLet::hir(cx, expr); - if !is_else_clause(cx.tcx, expr); - if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already - if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind; - if is_lang_ctor(cx, struct_qpath, OptionSome); - if let PatKind::Binding(bind_annotation, _, id, None) = &inner_pat.kind; + if !is_result_ok(cx, cond_expr); // Don't lint on Result::ok because a different lint does it already + let inner_pat = try_get_inner_pat(cx, pat)?; + if let PatKind::Binding(bind_annotation, _, id, None) = inner_pat.kind; if let Some(some_captures) = can_move_expr_to_closure(cx, if_then); if let Some(none_captures) = can_move_expr_to_closure(cx, if_else); if some_captures .iter() .filter_map(|(id, &c)| none_captures.get(id).map(|&c2| (c, c2))) .all(|(x, y)| x.is_imm_ref() && y.is_imm_ref()); - then { - let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; + let capture_mut = if bind_annotation == BindingAnnotation::Mutable { "mut " } else { "" }; let some_body = peel_blocks(if_then); let none_body = peel_blocks(if_else); let method_sugg = if eager_or_lazy::switch_to_eager_eval(cx, none_body) { "map_or" } else { "map_or_else" }; let capture_name = id.name.to_ident_string(); - let (as_ref, as_mut) = match &let_expr.kind { + let (as_ref, as_mut) = match &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 let_expr.kind { - // Pointer dereferencing happens automatically, so we can omit it in the suggestion - ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr, - _ => let_expr, + _ => (bind_annotation == BindingAnnotation::Ref, bind_annotation == BindingAnnotation::RefMut), }; + // Check if captures the closure will need conflict with borrows made in the scrutinee. // TODO: check all the references made in the scrutinee expression. This will require interacting // with the borrow checker. Currently only `<local>[.<field>]*` is checked for. @@ -154,33 +170,99 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> } } } - Some(OptionIfLetElseOccurrence { + + return Some(OptionOccurence { 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_with_macro_callsite(cx, some_body, "..")), none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")), - }) + }); + } + } + + None +} + +fn try_get_inner_pat<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<&'tcx Pat<'tcx>> { + if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind { + if is_lang_ctor(cx, qpath, OptionSome) { + return Some(inner_pat); + } + } + None +} + +/// If this expression is the option if let/else construct we're detecting, then +/// this function returns an `OptionOccurence` struct with details if +/// this construct is found, or None if this construct is not found. +fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurence> { + if let Some(higher::IfLet { + let_pat, + let_expr, + if_then, + if_else: Some(if_else), + }) = higher::IfLet::hir(cx, expr) + { + if !is_else_clause(cx.tcx, expr) { + return try_get_option_occurence(cx, let_pat, let_expr, if_then, if_else); + } + } + None +} + +fn detect_option_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurence> { + if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { + if let Some((let_pat, if_then, if_else)) = try_convert_match(cx, arms) { + return try_get_option_occurence(cx, let_pat, ex, if_then, if_else); + } + } + None +} + +fn try_convert_match<'tcx>( + cx: &LateContext<'tcx>, + arms: &[Arm<'tcx>], +) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if arms.len() == 2 { + return if is_none_arm(cx, &arms[1]) { + Some((arms[0].pat, arms[0].body, arms[1].body)) + } else if is_none_arm(cx, &arms[0]) { + Some((arms[1].pat, arms[1].body, arms[0].body)) } else { None - } + }; + } + None +} + +fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + match arm.pat.kind { + PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone), + PatKind::Wild => true, + _ => false, } } impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { - 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!( - "{}.{}({}, {})", - detection.option, detection.method_sugg, detection.none_expr, detection.some_expr, - ), - Applicability::MaybeIncorrect, - ); + // Don't lint macros, because it behaves weirdly + // and don't lint constant as well + if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) { + let detection = detect_option_if_let_else(cx, expr).or_else(|| detect_option_match(cx, expr)); + if let Some(det) = detection { + span_lint_and_sugg( + cx, + OPTION_IF_LET_ELSE, + expr.span, + format!("use Option::{} instead of an if let/else", det.method_sugg).as_str(), + "try", + format!( + "{}.{}({}, {})", + det.option, det.method_sugg, det.none_expr, det.some_expr + ), + Applicability::MaybeIncorrect, + ); + } } } } diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index b6d5e106f05..79610a0ea34 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -179,4 +179,19 @@ fn main() { let _ = pattern_to_vec("hello world"); let _ = complex_subpat(); + + // issue #8492 + let _ = s.map_or(1, |string| string.len()); + let _ = Some(10).map_or(5, |a| a + 1); + + let res: Result<i32, i32> = Ok(5); + let _ = match res { + Ok(a) => a + 1, + _ => 1, + }; + let _ = match res { + Err(_) => 1, + Ok(a) => a + 1, + }; + let _ = if let Ok(a) = res { a + 1 } else { 5 }; } diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 35bae159343..9eeaea12d3b 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -208,4 +208,25 @@ fn main() { let _ = pattern_to_vec("hello world"); let _ = complex_subpat(); + + // issue #8492 + let _ = match s { + Some(string) => string.len(), + None => 1, + }; + let _ = match Some(10) { + Some(a) => a + 1, + None => 5, + }; + + let res: Result<i32, i32> = Ok(5); + let _ = match res { + Ok(a) => a + 1, + _ => 1, + }; + let _ = match res { + Err(_) => 1, + Ok(a) => a + 1, + }; + let _ = if let Ok(a) = res { a + 1 } else { 5 }; } diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index daba606004e..4086b45b7c0 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -206,5 +206,25 @@ LL + s.len() + x LL ~ }); | -error: aborting due to 15 previous errors +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:212:13 + | +LL | let _ = match s { + | _____________^ +LL | | Some(string) => string.len(), +LL | | None => 1, +LL | | }; + | |_____^ help: try: `s.map_or(1, |string| string.len())` + +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:216:13 + | +LL | let _ = match Some(10) { + | _____________^ +LL | | Some(a) => a + 1, +LL | | None => 5, +LL | | }; + | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` + +error: aborting due to 17 previous errors |
