Skip to content

Commit 2590701

Browse files
committed
Auto merge of #8400 - Jarcho:split_matches, r=Manishearth
Split matches Part of #6680 changelog: None
2 parents 3d43826 + c65894c commit 2590701

16 files changed

+2548
-2445
lines changed

clippy_lints/src/matches.rs

-2,445
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs};
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{ExprKind, Local, MatchSource, PatKind, QPath};
6+
use rustc_lint::LateContext;
7+
8+
use super::INFALLIBLE_DESTRUCTURING_MATCH;
9+
10+
pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
11+
if_chain! {
12+
if !local.span.from_expansion();
13+
if let Some(expr) = local.init;
14+
if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
15+
if arms.len() == 1 && arms[0].guard.is_none();
16+
if let PatKind::TupleStruct(
17+
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
18+
if args.len() == 1;
19+
if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
20+
let body = peel_blocks(arms[0].body);
21+
if path_to_local_id(body, arg);
22+
23+
then {
24+
let mut applicability = Applicability::MachineApplicable;
25+
span_lint_and_sugg(
26+
cx,
27+
INFALLIBLE_DESTRUCTURING_MATCH,
28+
local.span,
29+
"you seem to be trying to use `match` to destructure a single infallible pattern. \
30+
Consider using `let`",
31+
"try this",
32+
format!(
33+
"let {}({}) = {};",
34+
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
35+
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
36+
snippet_with_applicability(cx, target.span, "..", &mut applicability),
37+
),
38+
applicability,
39+
);
40+
return true;
41+
}
42+
}
43+
false
44+
}
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::{is_lang_ctor, peel_blocks};
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, LangItem, PatKind, QPath};
6+
use rustc_lint::LateContext;
7+
use rustc_middle::ty;
8+
9+
use super::MATCH_AS_REF;
10+
11+
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
12+
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
13+
let arm_ref: Option<BindingAnnotation> = if is_none_arm(cx, &arms[0]) {
14+
is_ref_some_arm(cx, &arms[1])
15+
} else if is_none_arm(cx, &arms[1]) {
16+
is_ref_some_arm(cx, &arms[0])
17+
} else {
18+
None
19+
};
20+
if let Some(rb) = arm_ref {
21+
let suggestion = if rb == BindingAnnotation::Ref {
22+
"as_ref"
23+
} else {
24+
"as_mut"
25+
};
26+
27+
let output_ty = cx.typeck_results().expr_ty(expr);
28+
let input_ty = cx.typeck_results().expr_ty(ex);
29+
30+
let cast = if_chain! {
31+
if let ty::Adt(_, substs) = input_ty.kind();
32+
let input_ty = substs.type_at(0);
33+
if let ty::Adt(_, substs) = output_ty.kind();
34+
let output_ty = substs.type_at(0);
35+
if let ty::Ref(_, output_ty, _) = *output_ty.kind();
36+
if input_ty != output_ty;
37+
then {
38+
".map(|x| x as _)"
39+
} else {
40+
""
41+
}
42+
};
43+
44+
let mut applicability = Applicability::MachineApplicable;
45+
span_lint_and_sugg(
46+
cx,
47+
MATCH_AS_REF,
48+
expr.span,
49+
&format!("use `{}()` instead", suggestion),
50+
"try this",
51+
format!(
52+
"{}.{}(){}",
53+
snippet_with_applicability(cx, ex.span, "_", &mut applicability),
54+
suggestion,
55+
cast,
56+
),
57+
applicability,
58+
);
59+
}
60+
}
61+
}
62+
63+
// Checks if arm has the form `None => None`
64+
fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
65+
matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, LangItem::OptionNone))
66+
}
67+
68+
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
69+
fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> {
70+
if_chain! {
71+
if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
72+
if is_lang_ctor(cx, qpath, LangItem::OptionSome);
73+
if let PatKind::Binding(rb, .., ident, _) = first_pat.kind;
74+
if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
75+
if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind;
76+
if let ExprKind::Path(ref some_path) = e.kind;
77+
if is_lang_ctor(cx, some_path, LangItem::OptionSome) && args.len() == 1;
78+
if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind;
79+
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
80+
then {
81+
return Some(rb)
82+
}
83+
}
84+
None
85+
}
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::is_unit_expr;
3+
use clippy_utils::source::{expr_block, snippet};
4+
use clippy_utils::sugg::Sugg;
5+
use rustc_ast::LitKind;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Arm, Expr, ExprKind, PatKind};
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty;
10+
11+
use super::MATCH_BOOL;
12+
13+
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
14+
// Type of expression is `bool`.
15+
if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool {
16+
span_lint_and_then(
17+
cx,
18+
MATCH_BOOL,
19+
expr.span,
20+
"you seem to be trying to match on a boolean expression",
21+
move |diag| {
22+
if arms.len() == 2 {
23+
// no guards
24+
let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind {
25+
if let ExprKind::Lit(ref lit) = arm_bool.kind {
26+
match lit.node {
27+
LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
28+
LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
29+
_ => None,
30+
}
31+
} else {
32+
None
33+
}
34+
} else {
35+
None
36+
};
37+
38+
if let Some((true_expr, false_expr)) = exprs {
39+
let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
40+
(false, false) => Some(format!(
41+
"if {} {} else {}",
42+
snippet(cx, ex.span, "b"),
43+
expr_block(cx, true_expr, None, "..", Some(expr.span)),
44+
expr_block(cx, false_expr, None, "..", Some(expr.span))
45+
)),
46+
(false, true) => Some(format!(
47+
"if {} {}",
48+
snippet(cx, ex.span, "b"),
49+
expr_block(cx, true_expr, None, "..", Some(expr.span))
50+
)),
51+
(true, false) => {
52+
let test = Sugg::hir(cx, ex, "..");
53+
Some(format!(
54+
"if {} {}",
55+
!test,
56+
expr_block(cx, false_expr, None, "..", Some(expr.span))
57+
))
58+
},
59+
(true, true) => None,
60+
};
61+
62+
if let Some(sugg) = sugg {
63+
diag.span_suggestion(
64+
expr.span,
65+
"consider using an `if`/`else` expression",
66+
sugg,
67+
Applicability::HasPlaceholders,
68+
);
69+
}
70+
}
71+
}
72+
},
73+
);
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::{higher, is_wild};
4+
use rustc_ast::{Attribute, LitKind};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{BorrowKind, Expr, ExprKind, Guard, MatchSource, Pat};
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty;
9+
use rustc_span::source_map::Spanned;
10+
11+
use super::MATCH_LIKE_MATCHES_MACRO;
12+
13+
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
14+
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
15+
if let Some(higher::IfLet {
16+
let_pat,
17+
let_expr,
18+
if_then,
19+
if_else: Some(if_else),
20+
}) = higher::IfLet::hir(cx, expr)
21+
{
22+
return find_matches_sugg(
23+
cx,
24+
let_expr,
25+
IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
26+
expr,
27+
true,
28+
);
29+
}
30+
31+
if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind {
32+
return find_matches_sugg(
33+
cx,
34+
scrut,
35+
arms.iter().map(|arm| {
36+
(
37+
cx.tcx.hir().attrs(arm.hir_id),
38+
Some(arm.pat),
39+
arm.body,
40+
arm.guard.as_ref(),
41+
)
42+
}),
43+
expr,
44+
false,
45+
);
46+
}
47+
48+
false
49+
}
50+
51+
/// Lint a `match` or `if let` for replacement by `matches!`
52+
fn find_matches_sugg<'a, 'b, I>(
53+
cx: &LateContext<'_>,
54+
ex: &Expr<'_>,
55+
mut iter: I,
56+
expr: &Expr<'_>,
57+
is_if_let: bool,
58+
) -> bool
59+
where
60+
'b: 'a,
61+
I: Clone
62+
+ DoubleEndedIterator
63+
+ ExactSizeIterator
64+
+ Iterator<
65+
Item = (
66+
&'a [Attribute],
67+
Option<&'a Pat<'b>>,
68+
&'a Expr<'b>,
69+
Option<&'a Guard<'b>>,
70+
),
71+
>,
72+
{
73+
if_chain! {
74+
if iter.len() >= 2;
75+
if cx.typeck_results().expr_ty(expr).is_bool();
76+
if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
77+
let iter_without_last = iter.clone();
78+
if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
79+
if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
80+
if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
81+
if b0 != b1;
82+
if first_guard.is_none() || iter.len() == 0;
83+
if first_attrs.is_empty();
84+
if iter
85+
.all(|arm| {
86+
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
87+
});
88+
then {
89+
if let Some(last_pat) = last_pat_opt {
90+
if !is_wild(last_pat) {
91+
return false;
92+
}
93+
}
94+
95+
// The suggestion may be incorrect, because some arms can have `cfg` attributes
96+
// evaluated into `false` and so such arms will be stripped before.
97+
let mut applicability = Applicability::MaybeIncorrect;
98+
let pat = {
99+
use itertools::Itertools as _;
100+
iter_without_last
101+
.filter_map(|arm| {
102+
let pat_span = arm.1?.span;
103+
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
104+
})
105+
.join(" | ")
106+
};
107+
let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
108+
format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
109+
} else {
110+
pat
111+
};
112+
113+
// strip potential borrows (#6503), but only if the type is a reference
114+
let mut ex_new = ex;
115+
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
116+
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
117+
ex_new = ex_inner;
118+
}
119+
};
120+
span_lint_and_sugg(
121+
cx,
122+
MATCH_LIKE_MATCHES_MACRO,
123+
expr.span,
124+
&format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
125+
"try this",
126+
format!(
127+
"{}matches!({}, {})",
128+
if b0 { "" } else { "!" },
129+
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
130+
pat_and_guard,
131+
),
132+
applicability,
133+
);
134+
true
135+
} else {
136+
false
137+
}
138+
}
139+
}
140+
141+
/// Extract a `bool` or `{ bool }`
142+
fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
143+
match ex {
144+
ExprKind::Lit(Spanned {
145+
node: LitKind::Bool(b), ..
146+
}) => Some(*b),
147+
ExprKind::Block(
148+
rustc_hir::Block {
149+
stmts: &[],
150+
expr: Some(exp),
151+
..
152+
},
153+
_,
154+
) if is_if_let => {
155+
if let ExprKind::Lit(Spanned {
156+
node: LitKind::Bool(b), ..
157+
}) = exp.kind
158+
{
159+
Some(b)
160+
} else {
161+
None
162+
}
163+
},
164+
_ => None,
165+
}
166+
}

0 commit comments

Comments
 (0)