1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::source
::{expr_block, snippet}
;
3 use clippy_utils
::ty
::{implements_trait, match_type, peel_mid_ty_refs}
;
5 is_lint_allowed
, is_unit_expr
, is_wild
, paths
, peel_blocks
, peel_hir_pat_refs
, peel_n_hir_expr_refs
,
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind}
;
10 use rustc_lint
::LateContext
;
11 use rustc_middle
::ty
::{self, Ty}
;
13 use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}
;
16 pub(crate) fn check(cx
: &LateContext
<'_
>, ex
: &Expr
<'_
>, arms
: &[Arm
<'_
>], expr
: &Expr
<'_
>) {
17 if arms
.len() == 2 && arms
[0].guard
.is_none() && arms
[1].guard
.is_none() {
18 if expr
.span
.from_expansion() {
19 // Don't lint match expressions present in
23 if let PatKind
::Or(..) = arms
[0].pat
.kind
{
24 // don't lint for or patterns for now, this makes
25 // the lint noisy in unnecessary situations
28 let els
= arms
[1].body
;
29 let els
= if is_unit_expr(peel_blocks(els
)) {
31 } else if let ExprKind
::Block(Block { stmts, expr: block_expr, .. }
, _
) = els
.kind
{
32 if stmts
.len() == 1 && block_expr
.is_none() || stmts
.is_empty() && block_expr
.is_some() {
33 // single statement/expr "else" block, don't lint
36 // block with 2+ statements or 1 expr and 1+ statement
39 // not a block, don't lint
43 let ty
= cx
.typeck_results().expr_ty(ex
);
44 if *ty
.kind() != ty
::Bool
|| is_lint_allowed(cx
, MATCH_BOOL
, ex
.hir_id
) {
45 check_single_pattern(cx
, ex
, arms
, expr
, els
);
46 check_opt_like(cx
, ex
, arms
, expr
, ty
, els
);
51 fn check_single_pattern(
56 els
: Option
<&Expr
<'_
>>,
58 if is_wild(arms
[1].pat
) {
59 report_single_pattern(cx
, ex
, arms
, expr
, els
);
63 fn report_single_pattern(
68 els
: Option
<&Expr
<'_
>>,
70 let lint
= if els
.is_some() { SINGLE_MATCH_ELSE }
else { SINGLE_MATCH }
;
71 let els_str
= els
.map_or(String
::new(), |els
| {
72 format
!(" else {}", expr_block(cx
, els
, None
, "..", Some(expr
.span
)))
75 let (pat
, pat_ref_count
) = peel_hir_pat_refs(arms
[0].pat
);
76 let (msg
, sugg
) = if_chain
! {
77 if let PatKind
::Path(_
) | PatKind
::Lit(_
) = pat
.kind
;
78 let (ty
, ty_ref_count
) = peel_mid_ty_refs(cx
.typeck_results().expr_ty(ex
));
79 if let Some(spe_trait_id
) = cx
.tcx
.lang_items().structural_peq_trait();
80 if let Some(pe_trait_id
) = cx
.tcx
.lang_items().eq_trait();
81 if ty
.is_integral() || ty
.is_char() || ty
.is_str()
82 || (implements_trait(cx
, ty
, spe_trait_id
, &[])
83 && implements_trait(cx
, ty
, pe_trait_id
, &[ty
.into()]));
85 // scrutinee derives PartialEq and the pattern is a constant.
86 let pat_ref_count
= match pat
.kind
{
87 // string literals are already a reference.
88 PatKind
::Lit(Expr { kind: ExprKind::Lit(lit), .. }
) if lit
.node
.is_str() => pat_ref_count
+ 1,
91 // References are only implicitly added to the pattern, so no overflow here.
92 // e.g. will work: match &Some(_) { Some(_) => () }
93 // will not: match Some(_) { &Some(_) => () }
94 let ref_count_diff
= ty_ref_count
- pat_ref_count
;
96 // Try to remove address of expressions first.
97 let (ex
, removed
) = peel_n_hir_expr_refs(ex
, ref_count_diff
);
98 let ref_count_diff
= ref_count_diff
- removed
;
100 let msg
= "you seem to be trying to use `match` for an equality check. Consider using `if`";
102 "if {} == {}{} {}{}",
103 snippet(cx
, ex
.span
, ".."),
104 // PartialEq for different reference counts may not exist.
105 "&".repeat(ref_count_diff
),
106 snippet(cx
, arms
[0].pat
.span
, ".."),
107 expr_block(cx
, arms
[0].body
, None
, "..", Some(expr
.span
)),
112 let msg
= "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
114 "if let {} = {} {}{}",
115 snippet(cx
, arms
[0].pat
.span
, ".."),
116 snippet(cx
, ex
.span
, ".."),
117 expr_block(cx
, arms
[0].body
, None
, "..", Some(expr
.span
)),
131 Applicability
::HasPlaceholders
,
135 fn check_opt_like
<'a
>(
136 cx
: &LateContext
<'a
>,
141 els
: Option
<&Expr
<'_
>>,
143 // list of candidate `Enum`s we know will never get any more members
145 (&paths
::COW
, "Borrowed"),
146 (&paths
::COW
, "Cow::Borrowed"),
147 (&paths
::COW
, "Cow::Owned"),
148 (&paths
::COW
, "Owned"),
149 (&paths
::OPTION
, "None"),
150 (&paths
::RESULT
, "Err"),
151 (&paths
::RESULT
, "Ok"),
154 // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
155 // match with the second branch, without enum variants in matches.
156 if !contains_only_wilds(arms
[1].pat
) && !form_exhaustive_matches(arms
[0].pat
, arms
[1].pat
) {
160 let mut paths_and_types
= Vec
::new();
161 if !collect_pat_paths(&mut paths_and_types
, cx
, arms
[1].pat
, ty
) {
165 let in_candidate_enum
= |path_info
: &(String
, Ty
<'_
>)| -> bool
{
166 let (path
, ty
) = path_info
;
167 for &(ty_path
, pat_path
) in candidates
{
168 if path
== pat_path
&& match_type(cx
, *ty
, ty_path
) {
174 if paths_and_types
.iter().all(in_candidate_enum
) {
175 report_single_pattern(cx
, ex
, arms
, expr
, els
);
179 /// Collects paths and their types from the given patterns. Returns true if the given pattern could
180 /// be simplified, false otherwise.
181 fn collect_pat_paths
<'a
>(acc
: &mut Vec
<(String
, Ty
<'a
>)>, cx
: &LateContext
<'a
>, pat
: &Pat
<'_
>, ty
: Ty
<'a
>) -> bool
{
183 PatKind
::Wild
=> true,
184 PatKind
::Tuple(inner
, _
) => inner
.iter().all(|p
| {
185 let p_ty
= cx
.typeck_results().pat_ty(p
);
186 collect_pat_paths(acc
, cx
, p
, p_ty
)
188 PatKind
::TupleStruct(ref path
, ..) => {
189 let path
= rustc_hir_pretty
::to_string(rustc_hir_pretty
::NO_ANN
, |s
| {
190 s
.print_qpath(path
, false);
192 acc
.push((path
, ty
));
195 PatKind
::Binding(BindingAnnotation
::Unannotated
, .., ident
, None
) => {
196 acc
.push((ident
.to_string(), ty
));
199 PatKind
::Path(ref path
) => {
200 let path
= rustc_hir_pretty
::to_string(rustc_hir_pretty
::NO_ANN
, |s
| {
201 s
.print_qpath(path
, false);
203 acc
.push((path
, ty
));
210 /// Returns true if the given arm of pattern matching contains wildcard patterns.
211 fn contains_only_wilds(pat
: &Pat
<'_
>) -> bool
{
213 PatKind
::Wild
=> true,
214 PatKind
::Tuple(inner
, _
) | PatKind
::TupleStruct(_
, inner
, ..) => inner
.iter().all(contains_only_wilds
),
219 /// Returns true if the given patterns forms only exhaustive matches that don't contain enum
220 /// patterns without a wildcard.
221 fn form_exhaustive_matches(left
: &Pat
<'_
>, right
: &Pat
<'_
>) -> bool
{
222 match (&left
.kind
, &right
.kind
) {
223 (PatKind
::Wild
, _
) | (_
, PatKind
::Wild
) => true,
224 (PatKind
::Tuple(left_in
, left_pos
), PatKind
::Tuple(right_in
, right_pos
)) => {
225 // We don't actually know the position and the presence of the `..` (dotdot) operator
226 // in the arms, so we need to evaluate the correct offsets here in order to iterate in
227 // both arms at the same time.
230 if left_pos
.is_some() { 1 }
else { 0 }
233 if right_pos
.is_some() { 1 }
else { 0 }
236 let mut left_pos
= left_pos
.unwrap_or(usize::MAX
);
237 let mut right_pos
= right_pos
.unwrap_or(usize::MAX
);
238 let mut left_dot_space
= 0;
239 let mut right_dot_space
= 0;
241 let mut found_dotdot
= false;
244 if left_dot_space
< len
- left_in
.len() {
250 right_dot_space
+= 1;
251 if right_dot_space
< len
- right_in
.len() {
259 if !contains_only_wilds(&left_in
[i
- left_dot_space
])
260 && !contains_only_wilds(&right_in
[i
- right_dot_space
])