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};
};
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};
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
/// 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"
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;
}
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();
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);
#[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;
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,
};
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,
);
#[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;
}
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,
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)),
});
}
}
.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`
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()));
},
}
}
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;
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};
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 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);
};
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))
],)
);
}