Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split matches #8400

Merged
merged 15 commits into from
Feb 7, 2022
2,445 changes: 0 additions & 2,445 deletions clippy_lints/src/matches.rs

This file was deleted.

44 changes: 44 additions & 0 deletions clippy_lints/src/matches/infalliable_detructuring_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs};
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, Local, MatchSource, PatKind, QPath};
use rustc_lint::LateContext;

use super::INFALLIBLE_DESTRUCTURING_MATCH;

pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
if_chain! {
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();
if let PatKind::TupleStruct(
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
if args.len() == 1;
if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
let body = peel_blocks(arms[0].body);
if path_to_local_id(body, arg);

then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
INFALLIBLE_DESTRUCTURING_MATCH,
local.span,
"you seem to be trying to use `match` to destructure a single infallible pattern. \
Consider using `let`",
"try this",
format!(
"let {}({}) = {};",
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
snippet_with_applicability(cx, target.span, "..", &mut applicability),
),
applicability,
);
return true;
}
}
false
}
85 changes: 85 additions & 0 deletions clippy_lints/src/matches/match_as_ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_lang_ctor, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, LangItem, PatKind, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty;

use super::MATCH_AS_REF;

pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
let arm_ref: Option<BindingAnnotation> = if is_none_arm(cx, &arms[0]) {
is_ref_some_arm(cx, &arms[1])
} else if is_none_arm(cx, &arms[1]) {
is_ref_some_arm(cx, &arms[0])
} else {
None
};
if let Some(rb) = arm_ref {
let suggestion = if rb == BindingAnnotation::Ref {
"as_ref"
} else {
"as_mut"
};

let output_ty = cx.typeck_results().expr_ty(expr);
let input_ty = cx.typeck_results().expr_ty(ex);

let cast = if_chain! {
if let ty::Adt(_, substs) = input_ty.kind();
let input_ty = substs.type_at(0);
if let ty::Adt(_, substs) = output_ty.kind();
let output_ty = substs.type_at(0);
if let ty::Ref(_, output_ty, _) = *output_ty.kind();
if input_ty != output_ty;
then {
".map(|x| x as _)"
} else {
""
}
};

let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
MATCH_AS_REF,
expr.span,
&format!("use `{}()` instead", suggestion),
"try this",
format!(
"{}.{}(){}",
snippet_with_applicability(cx, ex.span, "_", &mut applicability),
suggestion,
cast,
),
applicability,
);
}
}
}

// 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, LangItem::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, LangItem::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, LangItem::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
}
75 changes: 75 additions & 0 deletions clippy_lints/src/matches/match_bool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_unit_expr;
use clippy_utils::source::{expr_block, snippet};
use clippy_utils::sugg::Sugg;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Arm, Expr, ExprKind, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;

use super::MATCH_BOOL;

pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
// Type of expression is `bool`.
if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool {
span_lint_and_then(
cx,
MATCH_BOOL,
expr.span,
"you seem to be trying to match on a boolean expression",
move |diag| {
if arms.len() == 2 {
// no guards
let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind {
if let ExprKind::Lit(ref lit) = arm_bool.kind {
match lit.node {
LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
_ => None,
}
} else {
None
}
} else {
None
};

if let Some((true_expr, false_expr)) = exprs {
let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
(false, false) => Some(format!(
"if {} {} else {}",
snippet(cx, ex.span, "b"),
expr_block(cx, true_expr, None, "..", Some(expr.span)),
expr_block(cx, false_expr, None, "..", Some(expr.span))
)),
(false, true) => Some(format!(
"if {} {}",
snippet(cx, ex.span, "b"),
expr_block(cx, true_expr, None, "..", Some(expr.span))
)),
(true, false) => {
let test = Sugg::hir(cx, ex, "..");
Some(format!(
"if {} {}",
!test,
expr_block(cx, false_expr, None, "..", Some(expr.span))
))
},
(true, true) => None,
};

if let Some(sugg) = sugg {
diag.span_suggestion(
expr.span,
"consider using an `if`/`else` expression",
sugg,
Applicability::HasPlaceholders,
);
}
}
}
},
);
}
}
166 changes: 166 additions & 0 deletions clippy_lints/src/matches/match_like_matches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{higher, is_wild};
use rustc_ast::{Attribute, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Guard, MatchSource, Pat};
use rustc_lint::LateContext;
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<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
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,
IntoIterator::into_iter([(&[][..], 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 `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 iter.len() >= 2;
if cx.typeck_results().expr_ty(expr).is_bool();
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;
if first_guard.is_none() || iter.len() == 0;
if first_attrs.is_empty();
if iter
.all(|arm| {
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(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 _;
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)) = first_guard {
format!("{} if {}", pat, 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 this",
format!(
"{}matches!({}, {})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
pat_and_guard,
),
applicability,
);
true
} else {
false
}
}
}

/// Extract a `bool` or `{ bool }`
fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
match ex {
ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
}) => Some(*b),
ExprKind::Block(
rustc_hir::Block {
stmts: &[],
expr: Some(exp),
..
},
_,
) if is_if_let => {
if let ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
}) = exp.kind
{
Some(b)
} else {
None
}
},
_ => None,
}
}
Loading