]> git.lizzy.rs Git - rust.git/blobdiff - src/tools/clippy/clippy_lints/src/matches.rs
Rollup merge of #91562 - dtolnay:asyncspace, r=Mark-Simulacrum
[rust.git] / src / tools / clippy / clippy_lints / src / matches.rs
index eb311983b29276ebf0b21b35ed9e0dcc46750af3..eacbfa54cf70f22ad9480dcf367de28233ad7af5 100644 (file)
@@ -8,12 +8,11 @@
 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
 use clippy_utils::visitors::is_local_used;
 use clippy_utils::{
-    get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild,
-    meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns,
-    remove_blocks, strip_pat_refs,
+    get_parent_expr, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs,
+    path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks,
+    strip_pat_refs,
 };
 use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
-use core::array;
 use core::iter::{once, ExactSizeIterator};
 use if_chain::if_chain;
 use rustc_ast::ast::{Attribute, LitKind};
@@ -26,7 +25,6 @@
 };
 use rustc_hir::{HirIdMap, HirIdSet};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
-use rustc_middle::lint::in_external_macro;
 use rustc_middle::ty::{self, Ty, TyS, VariantDef};
 use rustc_semver::RustcVersion;
 use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -34,8 +32,6 @@
 use rustc_span::sym;
 use std::cmp::Ordering;
 use std::collections::hash_map::Entry;
-use std::iter;
-use std::ops::Bound;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -60,6 +56,7 @@
     ///     bar(foo);
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub SINGLE_MATCH,
     style,
     "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
     ///     bar(&other_ref);
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub SINGLE_MATCH_ELSE,
     pedantic,
     "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
     ///     _ => frob(x),
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MATCH_REF_PATS,
     style,
     "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
     ///     bar();
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MATCH_BOOL,
     pedantic,
     "a `match` on a boolean expression instead of an `if..else` block"
     ///     _ => (),
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MATCH_OVERLAPPING_ARM,
     style,
     "a `match` with overlapping arms"
     ///     Err(_) => panic!("err"),
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MATCH_WILD_ERR_ARM,
     pedantic,
     "a `match` with `Err(_)` arm and take drastic actions"
     /// // Good
     /// let r: Option<&()> = x.as_ref();
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MATCH_AS_REF,
     complexity,
     "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
     ///     Foo::B(_) => {},
     /// }
     /// ```
+    #[clippy::version = "1.34.0"]
     pub WILDCARD_ENUM_MATCH_ARM,
     restriction,
     "a wildcard enum match arm using `_`"
     ///     Foo::C => {},
     /// }
     /// ```
+    #[clippy::version = "1.45.0"]
     pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
     pedantic,
     "a wildcard enum match for a single variant"
     ///     _ => {},
     /// }
     /// ```
+    #[clippy::version = "1.42.0"]
     pub WILDCARD_IN_OR_PATTERNS,
     complexity,
     "a wildcard pattern used with others patterns in same match arm"
     /// let wrapper = Wrapper::Data(42);
     /// let Wrapper::Data(data) = wrapper;
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub INFALLIBLE_DESTRUCTURING_MATCH,
     style,
     "a `match` statement with a single infallible arm instead of a `let`"
     /// // Good
     /// let (c, d) = (a, b);
     /// ```
+    #[clippy::version = "1.43.0"]
     pub MATCH_SINGLE_BINDING,
     complexity,
     "a match with a single binding instead of using `let` statement"
     ///     _ => {},
     /// }
     /// ```
+    #[clippy::version = "1.43.0"]
     pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
     restriction,
     "a match on a struct that binds all fields but still uses the wildcard pattern"
     /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
     /// Ok::<i32, i32>(42).is_ok();
     /// ```
+    #[clippy::version = "1.31.0"]
     pub REDUNDANT_PATTERN_MATCHING,
     style,
     "use the proper utility function avoiding an `if let`"
     /// // Good
     /// let a = matches!(x, Some(0));
     /// ```
+    #[clippy::version = "1.47.0"]
     pub MATCH_LIKE_MATCHES_MACRO,
     style,
     "a match that could be written with the matches! macro"
     ///     Quz => quz(),
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MATCH_SAME_ARMS,
     pedantic,
     "`match` with identical arm bodies"
@@ -602,7 +614,7 @@ pub fn new(msrv: Option<RustcVersion>) -> Self {
 
 impl<'tcx> LateLintPass<'tcx> for Matches {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
-        if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) {
+        if expr.span.from_expansion() {
             return;
         }
 
@@ -634,15 +646,11 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
         if let ExprKind::Match(ex, arms, _) = expr.kind {
             check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
         }
-        if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
-            check_match_ref_pats(cx, let_expr, once(let_pat), expr);
-        }
     }
 
     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
         if_chain! {
-            if !in_external_macro(cx.sess(), local.span);
-            if !in_macro(local.span);
+            if !local.span.from_expansion();
             if let Some(expr) = local.init;
             if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
             if arms.len() == 1 && arms[0].guard.is_none();
@@ -677,8 +685,7 @@ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
 
     fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
         if_chain! {
-            if !in_external_macro(cx.sess(), pat.span);
-            if !in_macro(pat.span);
+            if !pat.span.from_expansion();
             if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
             if let Some(def_id) = path.res.opt_def_id();
             let ty = cx.tcx.type_of(def_id);
@@ -705,7 +712,7 @@ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
 #[rustfmt::skip]
 fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
     if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
-        if in_macro(expr.span) {
+        if expr.span.from_expansion() {
             // Don't lint match expressions present in
             // macro_rules! block
             return;
@@ -1065,7 +1072,10 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
                 PatKind::Path(path) => {
                     #[allow(clippy::match_same_arms)]
                     let id = match cx.qpath_res(path, pat.hir_id) {
-                        Res::Def(DefKind::Const | DefKind::ConstParam | DefKind::AnonConst, _) => return,
+                        Res::Def(
+                            DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst,
+                            _,
+                        ) => return,
                         Res::Def(_, id) => id,
                         _ => return,
                     };
@@ -1303,7 +1313,7 @@ fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>)
         return find_matches_sugg(
             cx,
             let_expr,
-            array::IntoIter::new([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
+            IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
             expr,
             true,
         );
@@ -1448,7 +1458,7 @@ fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
 
 #[allow(clippy::too_many_lines)]
 fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
-    if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
+    if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
         return;
     }
 
@@ -1603,27 +1613,27 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'
     None
 }
 
-/// Gets all arms that are unbounded `PatRange`s.
+/// 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 = match lhs {
+                    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 = match rhs {
+                    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.int_value(cx, ty)?;
-                    let rhs_val = rhs.int_value(cx, ty)?;
+                    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 => Bound::Included(rhs_val),
-                        RangeEnd::Excluded => Bound::Excluded(rhs_val),
+                        RangeEnd::Included => EndBound::Included(rhs_val),
+                        RangeEnd::Excluded => EndBound::Excluded(rhs_val),
                     };
                     return Some(SpannedRange {
                         span: pat.span,
@@ -1635,7 +1645,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
                     let value = constant_full_int(cx, cx.typeck_results(), value)?;
                     return Some(SpannedRange {
                         span: pat.span,
-                        node: (value, Bound::Included(value)),
+                        node: (value, EndBound::Included(value)),
                     });
                 }
             }
@@ -1644,10 +1654,16 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
         .collect()
 }
 
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum EndBound<T> {
+    Included(T),
+    Excluded(T),
+}
+
 #[derive(Debug, Eq, PartialEq)]
-pub struct SpannedRange<T> {
+struct SpannedRange<T> {
     pub span: Span,
-    pub node: (T, Bound<T>),
+    pub node: (T, EndBound<T>),
 }
 
 // Checks if arm has the form `None => None`
@@ -1696,82 +1712,63 @@ fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
     ref_count > 1
 }
 
-pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
+fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
 where
     T: Copy + Ord,
 {
-    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
-    enum Kind<'a, T> {
-        Start(T, &'a SpannedRange<T>),
-        End(Bound<T>, &'a SpannedRange<T>),
+    #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+    enum BoundKind {
+        EndExcluded,
+        Start,
+        EndIncluded,
     }
 
-    impl<'a, T: Copy> Kind<'a, T> {
-        fn range(&self) -> &'a SpannedRange<T> {
-            match *self {
-                Kind::Start(_, r) | Kind::End(_, r) => r,
-            }
-        }
-
-        fn value(self) -> Bound<T> {
-            match self {
-                Kind::Start(t, _) => Bound::Included(t),
-                Kind::End(t, _) => t,
-            }
-        }
-    }
+    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+    struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange<T>);
 
-    impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, 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 Kind<'a, T> {
-        fn cmp(&self, other: &Self) -> Ordering {
-            match (self.value(), other.value()) {
-                (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b),
-                // Range patterns cannot be unbounded (yet)
-                (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(),
-                (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) {
-                    Ordering::Equal => Ordering::Greater,
-                    other => other,
-                },
-                (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) {
-                    Ordering::Equal => Ordering::Less,
-                    other => 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 in ranges {
-        values.push(Kind::Start(r.node.0, r));
-        values.push(Kind::End(r.node.1, r));
+    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();
 
-    for (a, b) in iter::zip(&values, values.iter().skip(1)) {
-        match (a, b) {
-            (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
-                if ra.node != rb.node {
-                    return Some((ra, rb));
-                }
-            },
-            (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
-            _ => {
-                // skip if the range `a` is completely included into the range `b`
-                if let Ordering::Equal | Ordering::Less = a.cmp(b) {
-                    let kind_a = Kind::End(a.range().node.1, a.range());
-                    let kind_b = Kind::End(b.range().node.1, b.range());
-                    if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) {
-                        return None;
+    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));
                 }
-                return Some((a.range(), b.range()));
             },
         }
     }
@@ -1783,7 +1780,8 @@ mod redundant_pattern_match {
     use super::REDUNDANT_PATTERN_MATCHING;
     use clippy_utils::diagnostics::span_lint_and_then;
     use clippy_utils::higher;
-    use clippy_utils::source::{snippet, snippet_with_applicability};
+    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::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
     use if_chain::if_chain;
@@ -1793,7 +1791,7 @@ mod redundant_pattern_match {
     use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
     use rustc_hir::{
         intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
-        Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath,
+        Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp,
     };
     use rustc_lint::LateContext;
     use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
@@ -2047,8 +2045,10 @@ fn find_sugg_for_if_let<'tcx>(
 
         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,
@@ -2067,12 +2067,15 @@ fn find_sugg_for_if_let<'tcx>(
                 // ^^^^^^^^^^^^^^^^^^^
                 let span = expr_span.until(op_span.shrink_to_hi());
 
-                let mut app = if needs_drop {
+                let app = if needs_drop {
                     Applicability::MaybeIncorrect
                 } else {
                     Applicability::MachineApplicable
                 };
-                let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
+
+                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);
 
@@ -2222,29 +2225,29 @@ fn test_overlapping() {
     };
 
     assert_eq!(None, overlapping::<u8>(&[]));
-    assert_eq!(None, overlapping(&[sp(1, Bound::Included(4))]));
+    assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))]));
     assert_eq!(
         None,
-        overlapping(&[sp(1, Bound::Included(4)), sp(5, Bound::Included(6))])
+        overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))])
     );
     assert_eq!(
         None,
         overlapping(&[
-            sp(1, Bound::Included(4)),
-            sp(5, Bound::Included(6)),
-            sp(10, Bound::Included(11))
+            sp(1, EndBound::Included(4)),
+            sp(5, EndBound::Included(6)),
+            sp(10, EndBound::Included(11))
         ],)
     );
     assert_eq!(
-        Some((&sp(1, Bound::Included(4)), &sp(3, Bound::Included(6)))),
-        overlapping(&[sp(1, Bound::Included(4)), sp(3, Bound::Included(6))])
+        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, Bound::Included(6)), &sp(6, Bound::Included(11)))),
+        Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))),
         overlapping(&[
-            sp(1, Bound::Included(4)),
-            sp(5, Bound::Included(6)),
-            sp(6, Bound::Included(11))
+            sp(1, EndBound::Included(4)),
+            sp(5, EndBound::Included(6)),
+            sp(6, EndBound::Included(11))
         ],)
     );
 }