| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::source::snippet; |
| use clippy_utils::visitors::{for_each_expr, is_local_used}; |
| use clippy_utils::{in_constant, path_to_local}; |
| use rustc_ast::{BorrowKind, LitKind}; |
| use rustc_errors::Applicability; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, Pat, PatKind, UnOp}; |
| use rustc_lint::LateContext; |
| use rustc_span::symbol::Ident; |
| use rustc_span::{Span, Symbol}; |
| use std::borrow::Cow; |
| use std::ops::ControlFlow; |
| |
| use super::REDUNDANT_GUARDS; |
| |
| pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { |
| for outer_arm in arms { |
| let Some(guard) = outer_arm.guard else { |
| continue; |
| }; |
| |
| // `Some(x) if matches!(x, y)` |
| if let ExprKind::Match( |
| scrutinee, |
| [ |
| arm, |
| Arm { |
| pat: Pat { |
| kind: PatKind::Wild, .. |
| }, |
| .. |
| }, |
| ], |
| MatchSource::Normal, |
| ) = guard.kind |
| && let Some(binding) = get_pat_binding(cx, scrutinee, outer_arm) |
| { |
| let pat_span = match (arm.pat.kind, binding.byref_ident) { |
| (PatKind::Ref(pat, _), Some(_)) => pat.span, |
| (PatKind::Ref(..), None) | (_, Some(_)) => continue, |
| _ => arm.pat.span, |
| }; |
| emit_redundant_guards( |
| cx, |
| outer_arm, |
| guard.span, |
| snippet(cx, pat_span, "<binding>"), |
| &binding, |
| arm.guard, |
| ); |
| } |
| // `Some(x) if let Some(2) = x` |
| else if let ExprKind::Let(let_expr) = guard.kind |
| && let Some(binding) = get_pat_binding(cx, let_expr.init, outer_arm) |
| { |
| let pat_span = match (let_expr.pat.kind, binding.byref_ident) { |
| (PatKind::Ref(pat, _), Some(_)) => pat.span, |
| (PatKind::Ref(..), None) | (_, Some(_)) => continue, |
| _ => let_expr.pat.span, |
| }; |
| emit_redundant_guards( |
| cx, |
| outer_arm, |
| let_expr.span, |
| snippet(cx, pat_span, "<binding>"), |
| &binding, |
| None, |
| ); |
| } |
| // `Some(x) if x == Some(2)` |
| // `Some(x) if Some(2) == x` |
| else if let ExprKind::Binary(bin_op, local, pat) = guard.kind |
| && matches!(bin_op.node, BinOpKind::Eq) |
| // Ensure they have the same type. If they don't, we'd need deref coercion which isn't |
| // possible (currently) in a pattern. In some cases, you can use something like |
| // `as_deref` or similar but in general, we shouldn't lint this as it'd create an |
| // extraordinary amount of FPs. |
| // |
| // This isn't necessary in the other two checks, as they must be a pattern already. |
| && cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat) |
| // Since we want to lint on both `x == Some(2)` and `Some(2) == x`, we might have to "swap" |
| // `local` and `pat`, depending on which side they are. |
| && let Some((binding, pat)) = get_pat_binding(cx, local, outer_arm) |
| .map(|binding| (binding, pat)) |
| .or_else(|| get_pat_binding(cx, pat, outer_arm).map(|binding| (binding, local))) |
| && expr_can_be_pat(cx, pat) |
| { |
| let pat_span = match (pat.kind, binding.byref_ident) { |
| (ExprKind::AddrOf(BorrowKind::Ref, _, expr), Some(_)) => expr.span, |
| (ExprKind::AddrOf(..), None) | (_, Some(_)) => continue, |
| _ => pat.span, |
| }; |
| emit_redundant_guards( |
| cx, |
| outer_arm, |
| guard.span, |
| snippet(cx, pat_span, "<binding>"), |
| &binding, |
| None, |
| ); |
| } else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind |
| && let Some(binding) = get_pat_binding(cx, recv, outer_arm) |
| { |
| check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding); |
| } |
| } |
| } |
| |
| fn check_method_calls<'tcx>( |
| cx: &LateContext<'tcx>, |
| arm: &Arm<'tcx>, |
| method: Symbol, |
| recv: &Expr<'_>, |
| args: &[Expr<'_>], |
| if_expr: &Expr<'_>, |
| binding: &PatBindingInfo, |
| ) { |
| let ty = cx.typeck_results().expr_ty(recv).peel_refs(); |
| let slice_like = ty.is_slice() || ty.is_array(); |
| |
| let sugg = if method == sym!(is_empty) { |
| // `s if s.is_empty()` becomes "" |
| // `arr if arr.is_empty()` becomes [] |
| |
| if ty.is_str() && !in_constant(cx, if_expr.hir_id) { |
| r#""""#.into() |
| } else if slice_like { |
| "[]".into() |
| } else { |
| return; |
| } |
| } else if slice_like |
| && let Some(needle) = args.first() |
| && let ExprKind::AddrOf(.., needle) = needle.kind |
| && let ExprKind::Array(needles) = needle.kind |
| && needles.iter().all(|needle| expr_can_be_pat(cx, needle)) |
| { |
| // `arr if arr.starts_with(&[123])` becomes [123, ..] |
| // `arr if arr.ends_with(&[123])` becomes [.., 123] |
| // `arr if arr.starts_with(&[])` becomes [..] (why would anyone write this?) |
| |
| let mut sugg = snippet(cx, needle.span, "<needle>").into_owned(); |
| |
| if needles.is_empty() { |
| sugg.insert_str(1, ".."); |
| } else if method == sym!(starts_with) { |
| sugg.insert_str(sugg.len() - 1, ", .."); |
| } else if method == sym!(ends_with) { |
| sugg.insert_str(1, ".., "); |
| } else { |
| return; |
| } |
| |
| sugg.into() |
| } else { |
| return; |
| }; |
| |
| emit_redundant_guards(cx, arm, if_expr.span, sugg, binding, None); |
| } |
| |
| struct PatBindingInfo { |
| span: Span, |
| byref_ident: Option<Ident>, |
| is_field: bool, |
| } |
| |
| fn get_pat_binding<'tcx>( |
| cx: &LateContext<'tcx>, |
| guard_expr: &Expr<'_>, |
| outer_arm: &Arm<'tcx>, |
| ) -> Option<PatBindingInfo> { |
| if let Some(local) = path_to_local(guard_expr) |
| && !is_local_used(cx, outer_arm.body, local) |
| { |
| let mut span = None; |
| let mut byref_ident = None; |
| let mut multiple_bindings = false; |
| // `each_binding` gives the `HirId` of the `Pat` itself, not the binding |
| outer_arm.pat.walk(|pat| { |
| if let PatKind::Binding(bind_annot, hir_id, ident, _) = pat.kind |
| && hir_id == local |
| { |
| if matches!(bind_annot.0, rustc_ast::ByRef::Yes) { |
| let _ = byref_ident.insert(ident); |
| } |
| // the second call of `replace()` returns a `Some(span)`, meaning a multi-binding pattern |
| if span.replace(pat.span).is_some() { |
| multiple_bindings = true; |
| return false; |
| } |
| } |
| true |
| }); |
| |
| // Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)` |
| if !multiple_bindings { |
| return span.map(|span| PatBindingInfo { |
| span, |
| byref_ident, |
| is_field: matches!(cx.tcx.parent_hir_node(local), Node::PatField(_)), |
| }); |
| } |
| } |
| |
| None |
| } |
| |
| fn emit_redundant_guards<'tcx>( |
| cx: &LateContext<'tcx>, |
| outer_arm: &Arm<'tcx>, |
| guard_span: Span, |
| binding_replacement: Cow<'static, str>, |
| pat_binding: &PatBindingInfo, |
| inner_guard: Option<&Expr<'_>>, |
| ) { |
| span_lint_and_then( |
| cx, |
| REDUNDANT_GUARDS, |
| guard_span.source_callsite(), |
| "redundant guard", |
| |diag| { |
| let suggestion_span = match *pat_binding { |
| PatBindingInfo { |
| span, |
| byref_ident: Some(ident), |
| is_field: true, |
| } => (span, format!("{ident}: {binding_replacement}")), |
| PatBindingInfo { |
| span, is_field: true, .. |
| } => (span.shrink_to_hi(), format!(": {binding_replacement}")), |
| PatBindingInfo { span, .. } => (span, binding_replacement.into_owned()), |
| }; |
| diag.multipart_suggestion_verbose( |
| "try", |
| vec![ |
| suggestion_span, |
| ( |
| guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()), |
| inner_guard.map_or_else(String::new, |guard| { |
| format!(" if {}", snippet(cx, guard.span, "<guard>")) |
| }), |
| ), |
| ], |
| Applicability::MaybeIncorrect, |
| ); |
| }, |
| ); |
| } |
| |
| /// Checks if the given `Expr` can also be represented as a `Pat`. |
| /// |
| /// All literals generally also work as patterns, however float literals are special. |
| /// They are currently (as of 2023/08/08) still allowed in patterns, but that will become |
| /// an error in the future, and rustc already actively warns against this (see rust#41620), |
| /// so we don't consider those as usable within patterns for linting purposes. |
| fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| for_each_expr(expr, |expr| { |
| if match expr.kind { |
| ExprKind::ConstBlock(..) => cx.tcx.features().inline_const_pat, |
| ExprKind::Call(c, ..) if let ExprKind::Path(qpath) = c.kind => { |
| // Allow ctors |
| matches!(cx.qpath_res(&qpath, c.hir_id), Res::Def(DefKind::Ctor(..), ..)) |
| }, |
| ExprKind::Path(qpath) => { |
| matches!( |
| cx.qpath_res(&qpath, expr.hir_id), |
| Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..), |
| ) |
| }, |
| ExprKind::AddrOf(..) |
| | ExprKind::Array(..) |
| | ExprKind::Tup(..) |
| | ExprKind::Struct(..) |
| | ExprKind::Unary(UnOp::Neg, _) => true, |
| ExprKind::Lit(lit) if !matches!(lit.node, LitKind::Float(..)) => true, |
| _ => false, |
| } { |
| return ControlFlow::Continue(()); |
| } |
| |
| ControlFlow::Break(()) |
| }) |
| .is_none() |
| } |