-
-#[allow(clippy::too_many_lines)]
-fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
- if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
- return;
- }
-
- // HACK:
- // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here
- // to prevent false positives as there is currently no better way to detect if code was excluded by
- // a macro. See PR #6435
- if_chain! {
- if let Some(match_snippet) = snippet_opt(cx, expr.span);
- if let Some(arm_snippet) = snippet_opt(cx, arms[0].span);
- if let Some(ex_snippet) = snippet_opt(cx, ex.span);
- let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, "");
- if rest_snippet.contains("=>");
- then {
- // The code it self contains another thick arrow "=>"
- // -> Either another arm or a comment
- return;
- }
- }
-
- let matched_vars = ex.span;
- let bind_names = arms[0].pat.span;
- let match_body = peel_blocks(arms[0].body);
- let mut snippet_body = if match_body.span.from_expansion() {
- Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
- } else {
- snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
- };
-
- // Do we need to add ';' to suggestion ?
- match match_body.kind {
- ExprKind::Block(block, _) => {
- // macro + expr_ty(body) == ()
- if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
- snippet_body.push(';');
- }
- },
- _ => {
- // expr_ty(body) == ()
- if cx.typeck_results().expr_ty(match_body).is_unit() {
- snippet_body.push(';');
- }
- },
- }
-
- let mut applicability = Applicability::MaybeIncorrect;
- match arms[0].pat.kind {
- PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
- // If this match is in a local (`let`) stmt
- let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
- (
- parent_let_node.span,
- format!(
- "let {} = {};\n{}let {} = {};",
- snippet_with_applicability(cx, bind_names, "..", &mut applicability),
- snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
- " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
- snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
- snippet_body
- ),
- )
- } else {
- // If we are in closure, we need curly braces around suggestion
- let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
- let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
- if let Some(parent_expr) = get_parent_expr(cx, expr) {
- if let ExprKind::Closure(..) = parent_expr.kind {
- cbrace_end = format!("\n{}}}", indent);
- // Fix body indent due to the closure
- indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
- cbrace_start = format!("{{\n{}", indent);
- }
- }
- // If the parent is already an arm, and the body is another match statement,
- // we need curly braces around suggestion
- let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
- if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
- if let ExprKind::Match(..) = arm.body.kind {
- cbrace_end = format!("\n{}}}", indent);
- // Fix body indent due to the match
- indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
- cbrace_start = format!("{{\n{}", indent);
- }
- }
- (
- expr.span,
- format!(
- "{}let {} = {};\n{}{}{}",
- cbrace_start,
- snippet_with_applicability(cx, bind_names, "..", &mut applicability),
- snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
- indent,
- snippet_body,
- cbrace_end
- ),
- )
- };
- span_lint_and_sugg(
- cx,
- MATCH_SINGLE_BINDING,
- target_span,
- "this match could be written as a `let` statement",
- "consider using `let` statement",
- sugg,
- applicability,
- );
- },
- PatKind::Wild => {
- if ex.can_have_side_effects() {
- let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
- let sugg = format!(
- "{};\n{}{}",
- snippet_with_applicability(cx, ex.span, "..", &mut applicability),
- indent,
- snippet_body
- );
- span_lint_and_sugg(
- cx,
- MATCH_SINGLE_BINDING,
- expr.span,
- "this match could be replaced by its scrutinee and body",
- "consider using the scrutinee and body instead",
- sugg,
- applicability,
- );
- } else {
- span_lint_and_sugg(
- cx,
- MATCH_SINGLE_BINDING,
- expr.span,
- "this match could be replaced by its body itself",
- "consider using the match body instead",
- snippet_body,
- Applicability::MachineApplicable,
- );
- }
- },
- _ => (),
- }
-}
-
-/// Returns true if the `ex` match expression is in a local (`let`) statement
-fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
- let map = &cx.tcx.hir();
- if_chain! {
- if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
- if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
- then {
- return Some(parent_let_expr);
- }
- }
- None
-}
-
-/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges.
-fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<FullInt>> {
- arms.iter()
- .filter_map(|arm| {
- if let Arm { pat, guard: None, .. } = *arm {
- if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
- let lhs_const = match lhs {
- Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0,
- None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?,
- };
- let rhs_const = match rhs {
- Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0,
- None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?,
- };
-
- let lhs_val = lhs_const.int_value(cx, ty)?;
- let rhs_val = rhs_const.int_value(cx, ty)?;
-
- let rhs_bound = match range_end {
- RangeEnd::Included => EndBound::Included(rhs_val),
- RangeEnd::Excluded => EndBound::Excluded(rhs_val),
- };
- return Some(SpannedRange {
- span: pat.span,
- node: (lhs_val, rhs_bound),
- });
- }
-
- if let PatKind::Lit(value) = pat.kind {
- let value = constant_full_int(cx, cx.typeck_results(), value)?;
- return Some(SpannedRange {
- span: pat.span,
- node: (value, EndBound::Included(value)),
- });
- }
- }
- None
- })
- .collect()
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum EndBound<T> {
- Included(T),
- Excluded(T),
-}
-
-#[derive(Debug, Eq, PartialEq)]
-struct SpannedRange<T> {
- pub span: Span,
- pub node: (T, EndBound<T>),
-}
-
-// Checks if arm has the form `None => None`
-fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
- matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
-}
-
-// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
-fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> {
- if_chain! {
- if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
- if is_lang_ctor(cx, qpath, OptionSome);
- if let PatKind::Binding(rb, .., ident, _) = first_pat.kind;
- if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
- if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind;
- if let ExprKind::Path(ref some_path) = e.kind;
- if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1;
- if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind;
- if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
- then {
- return Some(rb)
- }
- }
- None
-}
-
-fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
-where
- 'b: 'a,
- I: Iterator<Item = &'a Pat<'b>>,
-{
- let mut ref_count = 0;
- for opt in pats.map(|pat| match pat.kind {
- PatKind::Ref(..) => Some(true), // &-patterns
- PatKind::Wild => Some(false), // an "anything" wildcard is also fine
- _ => None, // any other pattern is not fine
- }) {
- if let Some(inner) = opt {
- if inner {
- ref_count += 1;
- }
- } else {
- return false;
- }
- }
- ref_count > 1
-}
-
-fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
-where
- T: Copy + Ord,
-{
- #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
- enum BoundKind {
- EndExcluded,
- Start,
- EndIncluded,
- }
-
- #[derive(Copy, Clone, Debug, Eq, PartialEq)]
- struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange<T>);
-
- impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
- }
-
- impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> {
- fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering {
- let RangeBound(self_value, self_kind, _) = *self;
- (self_value, self_kind).cmp(&(*other_value, *other_kind))
- }
- }
-
- let mut values = Vec::with_capacity(2 * ranges.len());
-
- for r @ SpannedRange { node: (start, end), .. } in ranges {
- values.push(RangeBound(*start, BoundKind::Start, r));
- values.push(match end {
- EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r),
- EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r),
- });
- }
-
- values.sort();
-
- let mut started = vec![];
-
- for RangeBound(_, kind, range) in values {
- match kind {
- BoundKind::Start => started.push(range),
- BoundKind::EndExcluded | BoundKind::EndIncluded => {
- let mut overlap = None;
-
- while let Some(last_started) = started.pop() {
- if last_started == range {
- break;
- }
- overlap = Some(last_started);
- }
-
- if let Some(first_overlapping) = overlap {
- return Some((range, first_overlapping));
- }
- },
- }
- }
-
- None
-}
-
-mod redundant_pattern_match {
- use super::REDUNDANT_PATTERN_MATCHING;
- use clippy_utils::diagnostics::span_lint_and_then;
- use clippy_utils::source::snippet;
- use clippy_utils::sugg::Sugg;
- use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
- use clippy_utils::{higher, match_def_path};
- use clippy_utils::{is_lang_ctor, is_trait_method, paths};
- use if_chain::if_chain;
- use rustc_ast::ast::LitKind;
- use rustc_data_structures::fx::FxHashSet;
- use rustc_errors::Applicability;
- use rustc_hir::LangItem::{OptionNone, PollPending};
- use rustc_hir::{
- intravisit::{walk_expr, Visitor},
- Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp,
- };
- use rustc_lint::LateContext;
- use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
- use rustc_span::sym;
-
- pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- if let Some(higher::IfLet {
- if_else,
- let_pat,
- let_expr,
- ..
- }) = higher::IfLet::hir(cx, expr)
- {
- find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
- }
- if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind {
- find_sugg_for_match(cx, expr, op, arms);
- }
- if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
- find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
- }
- }
-
- /// Checks if the drop order for a type matters. Some std types implement drop solely to
- /// deallocate memory. For these types, and composites containing them, changing the drop order
- /// won't result in any observable side effects.
- fn type_needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
- type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
- }
-
- fn type_needs_ordered_drop_inner<'tcx>(
- cx: &LateContext<'tcx>,
- ty: Ty<'tcx>,
- seen: &mut FxHashSet<Ty<'tcx>>,
- ) -> bool {
- if !seen.insert(ty) {
- return false;
- }
- if !ty.needs_drop(cx.tcx, cx.param_env) {
- false
- } else if !cx
- .tcx
- .lang_items()
- .drop_trait()
- .map_or(false, |id| implements_trait(cx, ty, id, &[]))
- {
- // This type doesn't implement drop, so no side effects here.
- // Check if any component type has any.
- match ty.kind() {
- ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
- ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen),
- ty::Adt(adt, subs) => adt
- .all_fields()
- .map(|f| f.ty(cx.tcx, subs))
- .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
- _ => true,
- }
- }
- // Check for std types which implement drop, but only for memory allocation.
- else if is_type_diagnostic_item(cx, ty, sym::Vec)
- || is_type_lang_item(cx, ty, LangItem::OwnedBox)
- || is_type_diagnostic_item(cx, ty, sym::Rc)
- || is_type_diagnostic_item(cx, ty, sym::Arc)
- || is_type_diagnostic_item(cx, ty, sym::cstring_type)
- || is_type_diagnostic_item(cx, ty, sym::BTreeMap)
- || is_type_diagnostic_item(cx, ty, sym::LinkedList)
- || match_type(cx, ty, &paths::WEAK_RC)
- || match_type(cx, ty, &paths::WEAK_ARC)
- {
- // Check all of the generic arguments.
- if let ty::Adt(_, subs) = ty.kind() {
- subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen))
- } else {
- true
- }
- } else {
- true
- }
- }
-
- // Extract the generic arguments out of a type
- fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
- if_chain! {
- if let ty::Adt(_, subs) = ty.kind();
- if let Some(sub) = subs.get(index);
- if let GenericArgKind::Type(sub_ty) = sub.unpack();
- then {
- Some(sub_ty)
- } else {
- None
- }
- }
- }
-
- // Checks if there are any temporaries created in the given expression for which drop order
- // matters.
- fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- struct V<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- res: bool,
- }
- impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- match expr.kind {
- // Taking the reference of a value leaves a temporary
- // e.g. In `&String::new()` the string is a temporary value.
- // Remaining fields are temporary values
- // e.g. In `(String::new(), 0).1` the string is a temporary value.
- ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
- if !matches!(expr.kind, ExprKind::Path(_)) {
- if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
- self.res = true;
- } else {
- self.visit_expr(expr);
- }
- }
- },
- // the base type is alway taken by reference.
- // e.g. In `(vec![0])[0]` the vector is a temporary value.
- ExprKind::Index(base, index) => {
- if !matches!(base.kind, ExprKind::Path(_)) {
- if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
- self.res = true;
- } else {
- self.visit_expr(base);
- }
- }
- self.visit_expr(index);
- },
- // Method calls can take self by reference.
- // e.g. In `String::new().len()` the string is a temporary value.
- ExprKind::MethodCall(_, [self_arg, args @ ..], _) => {
- if !matches!(self_arg.kind, ExprKind::Path(_)) {
- let self_by_ref = self
- .cx
- .typeck_results()
- .type_dependent_def_id(expr.hir_id)
- .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
- if self_by_ref
- && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg))
- {
- self.res = true;
- } else {
- self.visit_expr(self_arg);
- }
- }
- args.iter().for_each(|arg| self.visit_expr(arg));
- },
- // Either explicitly drops values, or changes control flow.
- ExprKind::DropTemps(_)
- | ExprKind::Ret(_)
- | ExprKind::Break(..)
- | ExprKind::Yield(..)
- | ExprKind::Block(Block { expr: None, .. }, _)
- | ExprKind::Loop(..) => (),
-
- // Only consider the final expression.
- ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
-
- _ => walk_expr(self, expr),
- }
- }
- }
-
- let mut v = V { cx, res: false };
- v.visit_expr(expr);
- v.res
- }
-
- fn find_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 (good_method, inner_ty) = match check_pat.kind {
- PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
- if let PatKind::Wild = sub_pat.kind {
- let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
- let Some(id) = res.opt_def_id().and_then(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
- let lang_items = cx.tcx.lang_items();
- if Some(id) == lang_items.result_ok_variant() {
- ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
- } else if Some(id) == lang_items.result_err_variant() {
- ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
- } else if Some(id) == lang_items.option_some_variant() {
- ("is_some()", op_ty)
- } else if Some(id) == lang_items.poll_ready_variant() {
- ("is_ready()", op_ty)
- } else if match_def_path(cx, id, &paths::IPADDR_V4) {
- ("is_ipv4()", op_ty)
- } else if match_def_path(cx, id, &paths::IPADDR_V6) {
- ("is_ipv6()", op_ty)
- } else {
- return;
- }
- } else {
- return;
- }
- },
- PatKind::Path(ref path) => {
- let method = if is_lang_ctor(cx, path, OptionNone) {
- "is_none()"
- } else if is_lang_ctor(cx, path, PollPending) {
- "is_pending()"
- } else {
- return;
- };
- // `None` and `Pending` don't have an inner type.
- (method, cx.tcx.types.unit)
- },
- _ => 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 = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
-
- // check that `while_let_on_iterator` lint does not trigger
- if_chain! {
- if keyword == "while";
- if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind;
- if method_path.ident.name == sym::next;
- if is_trait_method(cx, let_expr, sym::Iterator);
- then {
- 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;
-
- // if/while let ... = ... { ... }
- // ^^^
- let op_span = result_expr.span.source_callsite();
-
- // if/while let ... = ... { ... }
- // ^^^^^^^^^^^^^^^^^^^
- let span = expr_span.until(op_span.shrink_to_hi());
-
- let app = if needs_drop {
- Applicability::MaybeIncorrect
- } else {
- Applicability::MachineApplicable
- };
-
- let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
- .maybe_par()
- .to_string();
-
- diag.span_suggestion(span, "try this", 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");
- }
- },
- );
- }
-
- fn find_sugg_for_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);
-
- let found_good_method = match node_pair {
- (
- 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,
- &paths::RESULT_OK,
- &paths::RESULT_ERR,
- "is_ok()",
- "is_err()",
- )
- .or_else(|| {
- find_good_method_for_match(
- cx,
- arms,
- path_left,
- path_right,
- &paths::IPADDR_V4,
- &paths::IPADDR_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,
- &paths::OPTION_SOME,
- &paths::OPTION_NONE,
- "is_some()",
- "is_none()",
- )
- .or_else(|| {
- find_good_method_for_match(
- cx,
- arms,
- path_left,
- path_right,
- &paths::POLL_READY,
- &paths::POLL_PENDING,
- "is_ready()",
- "is_pending()",
- )
- })
- } else {
- None
- }
- },
- _ => None,
- };
-
- if let Some(good_method) = found_good_method {
- let span = expr.span.to(op.span);
- let result_expr = match &op.kind {
- ExprKind::AddrOf(_, _, borrowed) => borrowed,
- _ => op,
- };
- span_lint_and_then(
- cx,
- REDUNDANT_PATTERN_MATCHING,
- expr.span,
- &format!("redundant pattern matching, consider using `{}`", good_method),
- |diag| {
- diag.span_suggestion(
- span,
- "try this",
- format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
- Applicability::MaybeIncorrect, // snippet
- );
- },
- );
- }
- }
- }
-
- #[allow(clippy::too_many_arguments)]
- fn find_good_method_for_match<'a>(
- cx: &LateContext<'_>,
- arms: &[Arm<'_>],
- path_left: &QPath<'_>,
- path_right: &QPath<'_>,
- expected_left: &[&str],
- expected_right: &[&str],
- should_be_left: &'a str,
- should_be_right: &'a str,
- ) -> Option<&'a str> {
- let left_id = cx
- .typeck_results()
- .qpath_res(path_left, arms[0].pat.hir_id)
- .opt_def_id()?;
- let right_id = cx
- .typeck_results()
- .qpath_res(path_right, arms[1].pat.hir_id)
- .opt_def_id()?;
- let body_node_pair =
- if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
- (&(*arms[0].body).kind, &(*arms[1].body).kind)
- } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
- (&(*arms[1].body).kind, &(*arms[0].body).kind)
- } else {
- return None;
- };
-
- match body_node_pair {
- (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
- (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
- (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
- _ => None,
- },
- _ => None,
- }
- }
-}
-
-#[test]
-fn test_overlapping() {
- use rustc_span::source_map::DUMMY_SP;
-
- let sp = |s, e| SpannedRange {
- span: DUMMY_SP,
- node: (s, e),
- };
-
- assert_eq!(None, overlapping::<u8>(&[]));
- assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))]));
- assert_eq!(
- None,
- overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))])
- );
- assert_eq!(
- None,
- overlapping(&[
- sp(1, EndBound::Included(4)),
- sp(5, EndBound::Included(6)),
- sp(10, EndBound::Included(11))
- ],)
- );
- assert_eq!(
- Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))),
- overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))])
- );
- assert_eq!(
- Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))),
- overlapping(&[
- sp(1, EndBound::Included(4)),
- sp(5, EndBound::Included(6)),
- sp(6, EndBound::Included(11))
- ],)
- );
-}
-
-/// Implementation of `MATCH_SAME_ARMS`.
-fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
- if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind {
- let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
- let mut h = SpanlessHash::new(cx);
- h.hash_expr(arm.body);
- h.finish()
- };
-
- let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
- let min_index = usize::min(lindex, rindex);
- let max_index = usize::max(lindex, rindex);
-
- let mut local_map: HirIdMap<HirId> = HirIdMap::default();
- let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
- if_chain! {
- if let Some(a_id) = path_to_local(a);
- if let Some(b_id) = path_to_local(b);
- let entry = match local_map.entry(a_id) {
- Entry::Vacant(entry) => entry,
- // check if using the same bindings as before
- Entry::Occupied(entry) => return *entry.get() == b_id,
- };
- // the names technically don't have to match; this makes the lint more conservative
- if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
- if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b));
- if pat_contains_local(lhs.pat, a_id);
- if pat_contains_local(rhs.pat, b_id);
- then {
- entry.insert(b_id);
- true
- } else {
- false
- }
- }
- };
- // Arms with a guard are ignored, those can’t always be merged together
- // This is also the case for arms in-between each there is an arm with a guard
- (min_index..=max_index).all(|index| arms[index].guard.is_none())
- && SpanlessEq::new(cx)
- .expr_fallback(eq_fallback)
- .eq_expr(lhs.body, rhs.body)
- // these checks could be removed to allow unused bindings
- && bindings_eq(lhs.pat, local_map.keys().copied().collect())
- && bindings_eq(rhs.pat, local_map.values().copied().collect())
- };
-
- let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
- for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) {
- span_lint_and_then(
- cx,
- MATCH_SAME_ARMS,
- j.body.span,
- "this `match` has identical arm bodies",
- |diag| {
- diag.span_note(i.body.span, "same as this");
-
- // Note: this does not use `span_suggestion` on purpose:
- // there is no clean way
- // to remove the other arm. Building a span and suggest to replace it to ""
- // makes an even more confusing error message. Also in order not to make up a
- // span for the whole pattern, the suggestion is only shown when there is only
- // one pattern. The user should know about `|` if they are already using it…
-
- let lhs = snippet(cx, i.pat.span, "<pat1>");
- let rhs = snippet(cx, j.pat.span, "<pat2>");
-
- if let PatKind::Wild = j.pat.kind {
- // if the last arm is _, then i could be integrated into _
- // note that i.pat cannot be _, because that would mean that we're
- // hiding all the subsequent arms, and rust won't compile
- diag.span_note(
- i.body.span,
- &format!(
- "`{}` has the same arm body as the `_` wildcard, consider removing it",
- lhs
- ),
- );
- } else {
- diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,))
- .help("...or consider changing the match arm bodies");
- }
- },
- );
- }
- }
-}
-
-fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
- let mut result = false;
- pat.walk_short(|p| {
- result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
- !result
- });
- result
-}
-
-/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
-fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
- let mut result = true;
- pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
- result && ids.is_empty()
-}