use clippy_utils::diagnostics::{
multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
};
+use clippy_utils::higher;
use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{
- get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, 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, 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,
};
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::LitKind;
+use rustc_ast::ast::{Attribute, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::LangItem::{OptionNone, OptionSome};
check_match_single_binding(cx, ex, arms, expr);
}
}
- if let ExprKind::Match(ex, arms, _) = expr.kind {
- check_match_ref_pats(cx, ex, arms, expr);
+ if let ExprKind::Match(ref ex, ref 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_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
- if has_only_ref_pats(arms) {
- let mut suggs = Vec::with_capacity(arms.len() + 1);
- let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
- let span = ex.span.source_callsite();
- suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
- (
- "you don't need to add `&` to both the expression and the patterns",
- "try",
- )
- } else {
- let span = ex.span.source_callsite();
- suggs.push((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
- (
- "you don't need to add `&` to all patterns",
- "instead of prefixing all patterns with `&`, you can dereference the expression",
- )
- };
-
- suggs.extend(arms.iter().filter_map(|a| {
- if let PatKind::Ref(refp, _) = a.pat.kind {
- Some((a.pat.span, snippet(cx, refp.span, "..").to_string()))
- } else {
- None
- }
- }));
+fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>)
+where
+ 'b: 'a,
+ I: Clone + Iterator<Item = &'a Pat<'b>>,
+{
+ if !has_only_ref_pats(pats.clone()) {
+ return;
+ }
- span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| {
- if !expr.span.from_expansion() {
- multispan_sugg(diag, msg, suggs);
- }
- });
+ let (first_sugg, msg, title);
+ let span = ex.span.source_callsite();
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind {
+ first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
+ msg = "try";
+ title = "you don't need to add `&` to both the expression and the patterns";
+ } else {
+ first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
+ msg = "instead of prefixing all patterns with `&`, you can dereference the expression";
+ title = "you don't need to add `&` to all patterns";
}
+
+ let remaining_suggs = pats.filter_map(|pat| {
+ if let PatKind::Ref(ref refp, _) = pat.kind {
+ Some((pat.span, snippet(cx, refp.span, "..").to_string()))
+ } else {
+ None
+ }
+ });
+
+ span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| {
+ if !expr.span.from_expansion() {
+ multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs));
+ }
+ });
}
fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
- if let ExprKind::Match(ex, arms, ref match_source) = &expr.kind {
- match match_source {
- MatchSource::Normal => find_matches_sugg(cx, ex, arms, expr, false),
- MatchSource::IfLetDesugar { .. } => find_matches_sugg(cx, ex, arms, expr, true),
- _ => false,
- }
- } else {
- false
+ if let Some(higher::IfLet {
+ let_pat,
+ let_expr,
+ if_then,
+ if_else: Some(if_else),
+ }) = higher::IfLet::hir(cx, expr)
+ {
+ return find_matches_sugg(
+ cx,
+ let_expr,
+ array::IntoIter::new([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
+ expr,
+ true,
+ );
}
+
+ if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind {
+ return find_matches_sugg(
+ cx,
+ scrut,
+ arms.iter().map(|arm| {
+ (
+ cx.tcx.hir().attrs(arm.hir_id),
+ Some(arm.pat),
+ arm.body,
+ arm.guard.as_ref(),
+ )
+ }),
+ expr,
+ false,
+ );
+ }
+
+ false
}
-/// Lint a `match` or desugared `if let` for replacement by `matches!`
-fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, desugared: bool) -> bool {
+/// 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 Guard<'b>>,
+ ),
+ >,
+{
if_chain! {
- if arms.len() >= 2;
+ if iter.len() >= 2;
if cx.typeck_results().expr_ty(expr).is_bool();
- if let Some((b1_arm, b0_arms)) = arms.split_last();
- if let Some(b0) = find_bool_lit(&b0_arms[0].body.kind, desugared);
- if let Some(b1) = find_bool_lit(&b1_arm.body.kind, desugared);
- if is_wild(b1_arm.pat);
+ if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
+ let iter_without_last = iter.clone();
+ if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
+ if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
+ if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
if b0 != b1;
- let if_guard = &b0_arms[0].guard;
- if if_guard.is_none() || b0_arms.len() == 1;
- if cx.tcx.hir().attrs(b0_arms[0].hir_id).is_empty();
- if b0_arms[1..].iter()
+ if first_guard.is_none() || iter.len() == 0;
+ if first_attrs.is_empty();
+ if iter
.all(|arm| {
- find_bool_lit(&arm.body.kind, desugared).map_or(false, |b| b == b0) &&
- arm.guard.is_none() && cx.tcx.hir().attrs(arm.hir_id).is_empty()
+ find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
});
then {
+ if let Some(ref last_pat) = last_pat_opt {
+ if !is_wild(last_pat) {
+ 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 _;
- b0_arms.iter()
- .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability))
+ 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(Guard::If(g)) = if_guard {
+ let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
} else {
pat
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
- &format!("{} expression looks like `matches!` macro", if desugared { "if let .. else" } else { "match" }),
+ &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
"try this",
format!(
"{}matches!({}, {})",
}
/// Extract a `bool` or `{ bool }`
-fn find_bool_lit(ex: &ExprKind<'_>, desugared: bool) -> Option<bool> {
+fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
match ex {
ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
..
},
_,
- ) if desugared => {
+ ) if is_if_let => {
if let ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
}) = exp.kind
.collect()
}
-fn is_unit_expr(expr: &Expr<'_>) -> bool {
- match expr.kind {
- ExprKind::Tup(v) if v.is_empty() => true,
- ExprKind::Block(b, _) if b.stmts.is_empty() && b.expr.is_none() => true,
- _ => false,
- }
-}
-
// 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))
None
}
-fn has_only_ref_pats(arms: &[Arm<'_>]) -> bool {
- let mapped = arms
- .iter()
- .map(|a| {
- match a.pat.kind {
- PatKind::Ref(..) => Some(true), // &-patterns
- PatKind::Wild => Some(false), // an "anything" wildcard is also fine
- _ => None, // any other pattern is not fine
+fn has_only_ref_pats<'a, 'b, I>(pats: I) -> bool
+where
+ 'b: 'a,
+ I: Iterator<Item = &'a Pat<'b>>,
+{
+ let mut at_least_one_is_true = false;
+ 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 {
+ at_least_one_is_true = true;
}
- })
- .collect::<Option<Vec<bool>>>();
- // look for Some(v) where there's at least one true element
- mapped.map_or(false, |v| v.iter().any(|el| *el))
+ } else {
+ return false;
+ }
+ }
+ at_least_one_is_true
}
pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
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::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 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, PatKind, QPath,
+ Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath,
};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
use rustc_span::sym;
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
- match match_source {
- MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
- MatchSource::IfLetDesugar { contains_else_clause } => {
- find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause);
- },
- MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
- _ => {},
- }
+ 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)
}
}
fn find_sugg_for_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
- op: &'tcx Expr<'tcx>,
- arm: &Arm<'_>,
+ let_pat: &Pat<'_>,
+ let_expr: &'tcx Expr<'_>,
keyword: &'static str,
has_else: bool,
) {
// also look inside refs
- let mut kind = &arm.pat.kind;
+ let mut kind = &let_pat.kind;
// if we have &None for example, peel it so we can detect "if let None = x"
if let PatKind::Ref(inner, _mutability) = kind {
kind = &inner.kind;
}
- let op_ty = cx.typeck_results().expr_ty(op);
+ 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 kind {
// 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, op);
+ 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, _, _, _) = op.kind;
+ if let ExprKind::MethodCall(method_path, _, _, _) = let_expr.kind;
if method_path.ident.name == sym::next;
- if is_trait_method(cx, op, sym::Iterator);
+ if is_trait_method(cx, let_expr, sym::Iterator);
then {
return;
}
}
- let result_expr = match &op.kind {
+ let result_expr = match &let_expr.kind {
ExprKind::AddrOf(_, _, borrowed) => borrowed,
- _ => op,
+ _ => let_expr,
};
span_lint_and_then(
cx,
REDUNDANT_PATTERN_MATCHING,
- arm.pat.span,
+ let_pat.span,
&format!("redundant pattern matching, consider using `{}`", good_method),
|diag| {
- // while let ... = ... { ... }
+ // if/while let ... = ... { ... }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
let expr_span = expr.span;
- // while let ... = ... { ... }
+ // if/while let ... = ... { ... }
// ^^^
let op_span = result_expr.span.source_callsite();
- // while let ... = ... { ... }
+ // if/while let ... = ... { ... }
// ^^^^^^^^^^^^^^^^^^^
let span = expr_span.until(op_span.shrink_to_hi());