| use super::REDUNDANT_PATTERN_MATCHING; |
| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; |
| use clippy_utils::source::{snippet, walk_span_to_context}; |
| use clippy_utils::sugg::{make_unop, Sugg}; |
| use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop}; |
| use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr}; |
| use clippy_utils::{higher, is_expn_of, is_trait_method}; |
| use rustc_ast::ast::LitKind; |
| use rustc_errors::Applicability; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; |
| use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp}; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty::{self, GenericArgKind, Ty}; |
| use rustc_span::{sym, Span, Symbol}; |
| use std::fmt::Write; |
| use std::ops::ControlFlow; |
| |
| pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| if let Some(higher::WhileLet { |
| let_pat, |
| let_expr, |
| let_span, |
| .. |
| }) = higher::WhileLet::hir(expr) |
| { |
| find_method_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false); |
| find_if_let_true(cx, let_pat, let_expr, let_span); |
| } |
| } |
| |
| pub(super) fn check_if_let<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| pat: &'tcx Pat<'_>, |
| scrutinee: &'tcx Expr<'_>, |
| has_else: bool, |
| let_span: Span, |
| ) { |
| find_if_let_true(cx, pat, scrutinee, let_span); |
| find_method_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else); |
| } |
| |
| /// Looks for: |
| /// * `matches!(expr, true)` |
| pub fn check_matches_true<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| arm: &'tcx Arm<'_>, |
| scrutinee: &'tcx Expr<'_>, |
| ) { |
| find_match_true( |
| cx, |
| arm.pat, |
| scrutinee, |
| expr.span.source_callsite(), |
| "using `matches!` to pattern match a bool", |
| ); |
| } |
| |
| /// Looks for any of: |
| /// * `if let true = ...` |
| /// * `if let false = ...` |
| /// * `while let true = ...` |
| fn find_if_let_true<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, scrutinee: &'tcx Expr<'_>, let_span: Span) { |
| find_match_true(cx, pat, scrutinee, let_span, "using `if let` to pattern match a bool"); |
| } |
| |
| /// Common logic between `find_if_let_true` and `check_matches_true` |
| fn find_match_true<'tcx>( |
| cx: &LateContext<'tcx>, |
| pat: &'tcx Pat<'_>, |
| scrutinee: &'tcx Expr<'_>, |
| span: Span, |
| message: &str, |
| ) { |
| if let PatKind::Lit(lit) = pat.kind |
| && let ExprKind::Lit(lit) = lit.kind |
| && let LitKind::Bool(pat_is_true) = lit.node |
| { |
| let mut applicability = Applicability::MachineApplicable; |
| |
| let mut sugg = Sugg::hir_with_context( |
| cx, |
| scrutinee, |
| scrutinee.span.source_callsite().ctxt(), |
| "..", |
| &mut applicability, |
| ); |
| |
| if !pat_is_true { |
| sugg = make_unop("!", sugg); |
| } |
| |
| span_lint_and_sugg( |
| cx, |
| REDUNDANT_PATTERN_MATCHING, |
| span, |
| message, |
| "consider using the condition directly", |
| sugg.to_string(), |
| applicability, |
| ); |
| } |
| } |
| |
| // Extract the generic arguments out of a type |
| fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> { |
| if let ty::Adt(_, subs) = ty.kind() |
| && let Some(sub) = subs.get(index) |
| && let GenericArgKind::Type(sub_ty) = sub.unpack() |
| { |
| Some(sub_ty) |
| } else { |
| None |
| } |
| } |
| |
| fn find_method_and_type<'tcx>( |
| cx: &LateContext<'tcx>, |
| check_pat: &Pat<'_>, |
| op_ty: Ty<'tcx>, |
| ) -> Option<(&'static str, Ty<'tcx>)> { |
| match check_pat.kind { |
| PatKind::TupleStruct(ref qpath, args, rest) => { |
| let is_wildcard = matches!(args.first().map(|p| &p.kind), Some(PatKind::Wild)); |
| let is_rest = matches!((args, rest.as_opt_usize()), ([], Some(_))); |
| |
| if is_wildcard || is_rest { |
| let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id); |
| let id = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id))?; |
| let lang_items = cx.tcx.lang_items(); |
| if Some(id) == lang_items.result_ok_variant() { |
| Some(("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))) |
| } else if Some(id) == lang_items.result_err_variant() { |
| Some(("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))) |
| } else if Some(id) == lang_items.option_some_variant() { |
| Some(("is_some()", op_ty)) |
| } else if Some(id) == lang_items.poll_ready_variant() { |
| Some(("is_ready()", op_ty)) |
| } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V4))) { |
| Some(("is_ipv4()", op_ty)) |
| } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V6))) { |
| Some(("is_ipv6()", op_ty)) |
| } else { |
| None |
| } |
| } else { |
| None |
| } |
| }, |
| PatKind::Path(ref path) => { |
| if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(path, check_pat.hir_id) |
| && let Some(variant_id) = cx.tcx.opt_parent(ctor_id) |
| { |
| let method = if cx.tcx.lang_items().option_none_variant() == Some(variant_id) { |
| "is_none()" |
| } else if cx.tcx.lang_items().poll_pending_variant() == Some(variant_id) { |
| "is_pending()" |
| } else { |
| return None; |
| }; |
| // `None` and `Pending` don't have an inner type. |
| Some((method, cx.tcx.types.unit)) |
| } else { |
| None |
| } |
| }, |
| _ => None, |
| } |
| } |
| |
| fn find_method_sugg_for_if_let<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| let_pat: &Pat<'_>, |
| let_expr: &'tcx Expr<'_>, |
| keyword: &'static str, |
| has_else: bool, |
| ) { |
| // also look inside refs |
| // if we have &None for example, peel it so we can detect "if let None = x" |
| let check_pat = match let_pat.kind { |
| PatKind::Ref(inner, _mutability) => inner, |
| _ => let_pat, |
| }; |
| let op_ty = cx.typeck_results().expr_ty(let_expr); |
| // Determine which function should be used, and the type contained by the corresponding |
| // variant. |
| let Some((good_method, inner_ty)) = find_method_and_type(cx, check_pat, op_ty) else { |
| return; |
| }; |
| |
| // If this is the last expression in a block or there is an else clause then the whole |
| // type needs to be considered, not just the inner type of the branch being matched on. |
| // Note the last expression in a block is dropped after all local bindings. |
| let check_ty = if has_else |
| || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..))))) |
| { |
| op_ty |
| } else { |
| inner_ty |
| }; |
| |
| // All temporaries created in the scrutinee expression are dropped at the same time as the |
| // scrutinee would be, so they have to be considered as well. |
| // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held |
| // for the duration if body. |
| let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr); |
| |
| // check that `while_let_on_iterator` lint does not trigger |
| if keyword == "while" |
| && let ExprKind::MethodCall(method_path, ..) = let_expr.kind |
| && method_path.ident.name == sym::next |
| && is_trait_method(cx, let_expr, sym::Iterator) |
| { |
| return; |
| } |
| |
| let result_expr = match &let_expr.kind { |
| ExprKind::AddrOf(_, _, borrowed) => borrowed, |
| ExprKind::Unary(UnOp::Deref, deref) => deref, |
| _ => let_expr, |
| }; |
| |
| span_lint_and_then( |
| cx, |
| REDUNDANT_PATTERN_MATCHING, |
| let_pat.span, |
| &format!("redundant pattern matching, consider using `{good_method}`"), |
| |diag| { |
| // if/while let ... = ... { ... } |
| // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| let expr_span = expr.span; |
| let ctxt = expr.span.ctxt(); |
| |
| // if/while let ... = ... { ... } |
| // ^^^ |
| let Some(res_span) = walk_span_to_context(result_expr.span.source_callsite(), ctxt) else { |
| return; |
| }; |
| |
| // if/while let ... = ... { ... } |
| // ^^^^^^^^^^^^^^^^^^^^^^ |
| let span = expr_span.until(res_span.shrink_to_hi()); |
| |
| let mut app = if needs_drop { |
| Applicability::MaybeIncorrect |
| } else { |
| Applicability::MachineApplicable |
| }; |
| |
| let sugg = Sugg::hir_with_context(cx, result_expr, ctxt, "_", &mut app) |
| .maybe_par() |
| .to_string(); |
| |
| diag.span_suggestion(span, "try", format!("{keyword} {sugg}.{good_method}"), app); |
| |
| if needs_drop { |
| diag.note("this will change drop order of the result, as well as all temporaries"); |
| diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important"); |
| } |
| }, |
| ); |
| } |
| |
| pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { |
| if arms.len() == 2 { |
| let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); |
| |
| if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) { |
| let span = is_expn_of(expr.span, "matches").unwrap_or(expr.span.to(op.span)); |
| let result_expr = match &op.kind { |
| ExprKind::AddrOf(_, _, borrowed) => borrowed, |
| _ => op, |
| }; |
| let mut sugg = format!("{}.{good_method}", snippet(cx, result_expr.span, "_")); |
| |
| if let Some(guard) = maybe_guard { |
| // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! |
| // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, |
| // counter to the intuition that it should be `Guard::IfLet`, so we need another check |
| // to see that there aren't any let chains anywhere in the guard, as that would break |
| // if we suggest `t.is_none() && (let X = y && z)` for: |
| // `match t { None if let X = y && z => true, _ => false }` |
| let has_nested_let_chain = for_each_expr(guard, |expr| { |
| if matches!(expr.kind, ExprKind::Let(..)) { |
| ControlFlow::Break(()) |
| } else { |
| ControlFlow::Continue(()) |
| } |
| }) |
| .is_some(); |
| |
| if has_nested_let_chain { |
| return; |
| } |
| |
| let guard = Sugg::hir(cx, guard, ".."); |
| let _ = write!(sugg, " && {}", guard.maybe_par()); |
| } |
| |
| span_lint_and_sugg( |
| cx, |
| REDUNDANT_PATTERN_MATCHING, |
| span, |
| &format!("redundant pattern matching, consider using `{good_method}`"), |
| "try", |
| sugg, |
| Applicability::MachineApplicable, |
| ); |
| } |
| } |
| } |
| |
| fn found_good_method<'tcx>( |
| cx: &LateContext<'_>, |
| arms: &'tcx [Arm<'tcx>], |
| node: (&PatKind<'_>, &PatKind<'_>), |
| ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { |
| match node { |
| ( |
| PatKind::TupleStruct(ref path_left, patterns_left, _), |
| PatKind::TupleStruct(ref path_right, patterns_right, _), |
| ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { |
| if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { |
| find_good_method_for_match( |
| cx, |
| arms, |
| path_left, |
| path_right, |
| Item::Lang(ResultOk), |
| Item::Lang(ResultErr), |
| "is_ok()", |
| "is_err()", |
| ) |
| .or_else(|| { |
| find_good_method_for_match( |
| cx, |
| arms, |
| path_left, |
| path_right, |
| Item::Diag(sym::IpAddr, sym!(V4)), |
| Item::Diag(sym::IpAddr, sym!(V6)), |
| "is_ipv4()", |
| "is_ipv6()", |
| ) |
| }) |
| } else { |
| None |
| } |
| }, |
| (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) |
| | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) |
| if patterns.len() == 1 => |
| { |
| if let PatKind::Wild = patterns[0].kind { |
| find_good_method_for_match( |
| cx, |
| arms, |
| path_left, |
| path_right, |
| Item::Lang(OptionSome), |
| Item::Lang(OptionNone), |
| "is_some()", |
| "is_none()", |
| ) |
| .or_else(|| { |
| find_good_method_for_match( |
| cx, |
| arms, |
| path_left, |
| path_right, |
| Item::Lang(PollReady), |
| Item::Lang(PollPending), |
| "is_ready()", |
| "is_pending()", |
| ) |
| }) |
| } else { |
| None |
| } |
| }, |
| (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { |
| if let PatKind::Wild = patterns[0].kind { |
| get_good_method(cx, arms, path_left) |
| } else { |
| None |
| } |
| }, |
| (PatKind::Path(ref path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), |
| _ => None, |
| } |
| } |
| |
| fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> { |
| match path { |
| QPath::Resolved(_, path) => { |
| let name = path.segments[0].ident; |
| Some(name) |
| }, |
| _ => None, |
| } |
| } |
| |
| fn get_good_method<'tcx>( |
| cx: &LateContext<'_>, |
| arms: &'tcx [Arm<'tcx>], |
| path_left: &QPath<'_>, |
| ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { |
| if let Some(name) = get_ident(path_left) { |
| let (expected_item_left, should_be_left, should_be_right) = match name.as_str() { |
| "Ok" => (Item::Lang(ResultOk), "is_ok()", "is_err()"), |
| "Err" => (Item::Lang(ResultErr), "is_err()", "is_ok()"), |
| "Some" => (Item::Lang(OptionSome), "is_some()", "is_none()"), |
| "None" => (Item::Lang(OptionNone), "is_none()", "is_some()"), |
| "Ready" => (Item::Lang(PollReady), "is_ready()", "is_pending()"), |
| "Pending" => (Item::Lang(PollPending), "is_pending()", "is_ready()"), |
| "V4" => (Item::Diag(sym::IpAddr, sym!(V4)), "is_ipv4()", "is_ipv6()"), |
| "V6" => (Item::Diag(sym::IpAddr, sym!(V6)), "is_ipv6()", "is_ipv4()"), |
| _ => return None, |
| }; |
| return find_good_method_for_matches_macro( |
| cx, |
| arms, |
| path_left, |
| expected_item_left, |
| should_be_left, |
| should_be_right, |
| ); |
| } |
| None |
| } |
| |
| #[derive(Clone, Copy)] |
| enum Item { |
| Lang(LangItem), |
| Diag(Symbol, Symbol), |
| } |
| |
| fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expected_item: Item) -> bool { |
| let Some(id) = cx.typeck_results().qpath_res(path, pat.hir_id).opt_def_id() else { |
| return false; |
| }; |
| |
| match expected_item { |
| Item::Lang(expected_lang_item) => cx |
| .tcx |
| .lang_items() |
| .get(expected_lang_item) |
| .map_or(false, |expected_id| cx.tcx.parent(id) == expected_id), |
| Item::Diag(expected_ty, expected_variant) => { |
| let ty = cx.typeck_results().pat_ty(pat); |
| |
| if is_type_diagnostic_item(cx, ty, expected_ty) { |
| let variant = ty |
| .ty_adt_def() |
| .expect("struct pattern type is not an ADT") |
| .variant_of_res(cx.qpath_res(path, pat.hir_id)); |
| |
| return variant.name == expected_variant; |
| } |
| |
| false |
| }, |
| } |
| } |
| |
| #[expect(clippy::too_many_arguments)] |
| fn find_good_method_for_match<'a, 'tcx>( |
| cx: &LateContext<'_>, |
| arms: &'tcx [Arm<'tcx>], |
| path_left: &QPath<'_>, |
| path_right: &QPath<'_>, |
| expected_item_left: Item, |
| expected_item_right: Item, |
| should_be_left: &'a str, |
| should_be_right: &'a str, |
| ) -> Option<(&'a str, Option<&'tcx Expr<'tcx>>)> { |
| let first_pat = arms[0].pat; |
| let second_pat = arms[1].pat; |
| |
| let body_node_pair = if (is_pat_variant(cx, first_pat, path_left, expected_item_left)) |
| && (is_pat_variant(cx, second_pat, path_right, expected_item_right)) |
| { |
| (&arms[0].body.kind, &arms[1].body.kind) |
| } else if (is_pat_variant(cx, first_pat, path_left, expected_item_right)) |
| && (is_pat_variant(cx, second_pat, path_right, expected_item_left)) |
| { |
| (&arms[1].body.kind, &arms[0].body.kind) |
| } else { |
| return None; |
| }; |
| |
| match body_node_pair { |
| (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { |
| (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard)), |
| (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard)), |
| _ => None, |
| }, |
| _ => None, |
| } |
| } |
| |
| fn find_good_method_for_matches_macro<'a, 'tcx>( |
| cx: &LateContext<'_>, |
| arms: &'tcx [Arm<'tcx>], |
| path_left: &QPath<'_>, |
| expected_item_left: Item, |
| should_be_left: &'a str, |
| should_be_right: &'a str, |
| ) -> Option<(&'a str, Option<&'tcx Expr<'tcx>>)> { |
| let first_pat = arms[0].pat; |
| |
| let body_node_pair = if is_pat_variant(cx, first_pat, path_left, expected_item_left) { |
| (&arms[0].body.kind, &arms[1].body.kind) |
| } else { |
| return None; |
| }; |
| |
| match body_node_pair { |
| (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { |
| (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard)), |
| (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard)), |
| _ => None, |
| }, |
| _ => None, |
| } |
| } |