blob: b062e81cefddd091a4a3f01e01d8211912b29000 [file] [log] [blame]
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment};
use rustc_ast::{Attribute, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Pat, PatKind, QPath};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty;
use rustc_span::source_map::Spanned;
use super::MATCH_LIKE_MATCHES_MACRO;
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
pub(crate) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
let_pat: &'tcx Pat<'_>,
let_expr: &'tcx Expr<'_>,
then_expr: &'tcx Expr<'_>,
else_expr: &'tcx Expr<'_>,
) {
find_matches_sugg(
cx,
let_expr,
IntoIterator::into_iter([
(&[][..], Some(let_pat), then_expr, None),
(&[][..], None, else_expr, None),
]),
expr,
true,
);
}
pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
scrutinee: &'tcx Expr<'_>,
arms: &'tcx [Arm<'tcx>],
) -> bool {
find_matches_sugg(
cx,
scrutinee,
arms.iter()
.map(|arm| (cx.tcx.hir().attrs(arm.hir_id), Some(arm.pat), arm.body, arm.guard)),
e,
false,
)
}
/// Lint a `match` or `if let` for replacement by `matches!`
fn find_matches_sugg<'a, 'b, I>(
cx: &LateContext<'_>,
ex: &Expr<'_>,
mut iter: I,
expr: &Expr<'_>,
is_if_let: bool,
) -> bool
where
'b: 'a,
I: Clone
+ DoubleEndedIterator
+ ExactSizeIterator
+ Iterator<Item = (&'a [Attribute], Option<&'a Pat<'b>>, &'a Expr<'b>, Option<&'a Expr<'b>>)>,
{
if !span_contains_comment(cx.sess().source_map(), expr.span)
&& iter.len() >= 2
&& cx.typeck_results().expr_ty(expr).is_bool()
&& let Some((_, last_pat_opt, last_expr, _)) = iter.next_back()
&& let iter_without_last = iter.clone()
&& let Some((first_attrs, _, first_expr, first_guard)) = iter.next()
&& let Some(b0) = find_bool_lit(&first_expr.kind)
&& let Some(b1) = find_bool_lit(&last_expr.kind)
&& b0 != b1
&& (first_guard.is_none() || iter.len() == 0)
&& first_attrs.is_empty()
&& iter.all(|arm| find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty())
{
if let Some(last_pat) = last_pat_opt {
if !is_wild(last_pat) {
return false;
}
}
for arm in iter_without_last.clone() {
if let Some(pat) = arm.1 {
if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some(pat.kind) {
return false;
}
}
}
// The suggestion may be incorrect, because some arms can have `cfg` attributes
// evaluated into `false` and so such arms will be stripped before.
let mut applicability = Applicability::MaybeIncorrect;
let pat = {
use itertools::Itertools as _;
iter_without_last
.filter_map(|arm| {
let pat_span = arm.1?.span;
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
})
.join(" | ")
};
let pat_and_guard = if let Some(g) = first_guard {
format!(
"{pat} if {}",
snippet_with_applicability(cx, g.span, "..", &mut applicability)
)
} else {
pat
};
// strip potential borrows (#6503), but only if the type is a reference
let mut ex_new = ex;
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
ex_new = ex_inner;
}
};
span_lint_and_sugg(
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
&format!(
"{} expression looks like `matches!` macro",
if is_if_let { "if let .. else" } else { "match" }
),
"try",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
true
} else {
false
}
}
/// Extract a `bool` or `{ bool }`
fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> {
match ex {
ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
}) => Some(*b),
ExprKind::Block(
rustc_hir::Block {
stmts: &[],
expr: Some(exp),
..
},
_,
) => {
if let ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
}) = exp.kind
{
Some(*b)
} else {
None
}
},
_ => None,
}
}
fn is_some(path_kind: PatKind<'_>) -> bool {
match path_kind {
PatKind::TupleStruct(QPath::Resolved(_, path), [first, ..], _) if is_wild(first) => {
let name = path.segments[0].ident;
name.name == rustc_span::sym::Some
},
_ => false,
}
}