Skip to content

Commit

Permalink
Auto merge of rust-lang#8400 - Jarcho:split_matches, r=Manishearth
Browse files Browse the repository at this point in the history
Split matches

Part of rust-lang#6680

changelog: None
  • Loading branch information
bors committed Feb 7, 2022
2 parents 3d43826 + c65894c commit 2590701
Show file tree
Hide file tree
Showing 16 changed files with 2,548 additions and 2,445 deletions.
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

0 comments on commit 2590701

Please sign in to comment.