1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: visitors:: LocalUsedVisitor ;
3
- use clippy_utils:: { is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq } ;
3
+ use clippy_utils:: { higher , is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq } ;
4
4
use if_chain:: if_chain;
5
5
use rustc_hir:: LangItem :: OptionNone ;
6
- use rustc_hir:: { Arm , Expr , ExprKind , Guard , HirId , Pat , PatKind , StmtKind } ;
6
+ use rustc_hir:: { Expr , ExprKind , Guard , HirId , Pat , PatKind , StmtKind } ;
7
7
use rustc_lint:: { LateContext , LateLintPass } ;
8
8
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9
9
use rustc_span:: { MultiSpan , Span } ;
@@ -49,22 +49,44 @@ declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
49
49
50
50
impl < ' tcx > LateLintPass < ' tcx > for CollapsibleMatch {
51
51
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
52
+ if let Some ( higher:: IfLet {
53
+ let_pat,
54
+ if_then,
55
+ if_else,
56
+ ..
57
+ } ) = higher:: IfLet :: hir ( expr)
58
+ {
59
+ check_arm ( cx, if_then, None , let_pat, if_else) ;
60
+
61
+ check_if_let ( cx, if_then, let_pat) ;
62
+ }
63
+
52
64
if let ExprKind :: Match ( _expr, arms, _source) = expr. kind {
53
- if let Some ( wild_arm) = arms. iter ( ) . rfind ( |arm| arm_is_wild_like ( cx, arm) ) {
65
+ if let Some ( wild_arm) = arms. iter ( ) . rfind ( |arm| is_wild_like ( cx, & arm. pat . kind , & arm . guard ) ) {
54
66
for arm in arms {
55
- check_arm ( arm, wild_arm , cx ) ;
67
+ check_arm ( cx , arm. body , arm . guard . as_ref ( ) , arm . pat , Some ( wild_arm . body ) ) ;
56
68
}
57
69
}
70
+
71
+ if let Some ( first_arm) = arms. get ( 0 ) {
72
+ check_if_let ( cx, & first_arm. body , & first_arm. pat ) ;
73
+ }
58
74
}
59
75
}
60
76
}
61
77
62
- fn check_arm < ' tcx > ( arm : & Arm < ' tcx > , wild_outer_arm : & Arm < ' tcx > , cx : & LateContext < ' tcx > ) {
63
- let expr = strip_singleton_blocks ( arm. body ) ;
78
+ fn check_arm < ' tcx > (
79
+ cx : & LateContext < ' tcx > ,
80
+ outer_block : & ' tcx Expr < ' tcx > ,
81
+ outer_guard : Option < & Guard < ' tcx > > ,
82
+ outer_pat : & ' tcx Pat < ' tcx > ,
83
+ wild_outer_block : Option < & ' tcx Expr < ' tcx > > ,
84
+ ) {
85
+ let expr = strip_singleton_blocks ( outer_block) ;
64
86
if_chain ! {
65
87
if let ExprKind :: Match ( expr_in, arms_inner, _) = expr. kind;
66
88
// the outer arm pattern and the inner match
67
- if expr_in. span. ctxt( ) == arm . pat . span. ctxt( ) ;
89
+ if expr_in. span. ctxt( ) == outer_pat . span. ctxt( ) ;
68
90
// there must be no more than two arms in the inner match for this lint
69
91
if arms_inner. len( ) == 2 ;
70
92
// no if guards on the inner match
@@ -73,18 +95,18 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
73
95
// match <local> { .. }
74
96
if let Some ( binding_id) = path_to_local( peel_ref_operators( cx, expr_in) ) ;
75
97
// one of the branches must be "wild-like"
76
- if let Some ( wild_inner_arm_idx) = arms_inner. iter( ) . rposition( |arm_inner| arm_is_wild_like ( cx, arm_inner) ) ;
98
+ if let Some ( wild_inner_arm_idx) = arms_inner. iter( ) . rposition( |arm_inner| is_wild_like ( cx, & arm_inner. pat . kind , & arm_inner . guard ) ) ;
77
99
let ( wild_inner_arm, non_wild_inner_arm) =
78
100
( & arms_inner[ wild_inner_arm_idx] , & arms_inner[ 1 - wild_inner_arm_idx] ) ;
79
101
if !pat_contains_or( non_wild_inner_arm. pat) ;
80
102
// the binding must come from the pattern of the containing match arm
81
103
// ..<local>.. => match <local> { .. }
82
- if let Some ( binding_span) = find_pat_binding( arm . pat , binding_id) ;
104
+ if let Some ( binding_span) = find_pat_binding( outer_pat , binding_id) ;
83
105
// the "wild-like" branches must be equal
84
- if SpanlessEq :: new( cx) . eq_expr( wild_inner_arm. body, wild_outer_arm . body ) ;
106
+ if wild_outer_block . map ( |el| SpanlessEq :: new( cx) . eq_expr( wild_inner_arm. body, el ) ) . unwrap_or ( true ) ;
85
107
// the binding must not be used in the if guard
86
108
let mut used_visitor = LocalUsedVisitor :: new( cx, binding_id) ;
87
- if match arm . guard {
109
+ if match outer_guard {
88
110
None => true ,
89
111
Some ( Guard :: If ( expr) | Guard :: IfLet ( _, expr) ) => !used_visitor. check_expr( expr) ,
90
112
} ;
@@ -107,6 +129,31 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
107
129
}
108
130
}
109
131
132
+ fn check_if_let < ' tcx > ( cx : & LateContext < ' tcx > , outer_expr : & ' tcx Expr < ' tcx > , outer_pat : & ' tcx Pat < ' tcx > ) {
133
+ let block_inner = strip_singleton_blocks ( outer_expr) ;
134
+ if_chain ! {
135
+ if let Some ( higher:: IfLet { if_then: inner_if_then, let_expr: inner_let_expr, let_pat: inner_let_pat, .. } ) = higher:: IfLet :: hir( block_inner) ;
136
+ if let Some ( binding_id) = path_to_local( peel_ref_operators( cx, inner_let_expr) ) ;
137
+ if let Some ( binding_span) = find_pat_binding( outer_pat, binding_id) ;
138
+ let mut used_visitor = LocalUsedVisitor :: new( cx, binding_id) ;
139
+ if !used_visitor. check_expr( inner_if_then) ;
140
+ then {
141
+ span_lint_and_then(
142
+ cx,
143
+ COLLAPSIBLE_MATCH ,
144
+ block_inner. span,
145
+ "unnecessary nested `if let` or `match`" ,
146
+ |diag| {
147
+ let mut help_span = MultiSpan :: from_spans( vec![ binding_span, inner_let_pat. span] ) ;
148
+ help_span. push_span_label( binding_span, "replace this binding" . into( ) ) ;
149
+ help_span. push_span_label( inner_let_pat. span, "with this pattern" . into( ) ) ;
150
+ diag. span_help( help_span, "the outer pattern can be modified to include the inner pattern" ) ;
151
+ } ,
152
+ ) ;
153
+ }
154
+ }
155
+ }
156
+
110
157
fn strip_singleton_blocks < ' hir > ( mut expr : & ' hir Expr < ' hir > ) -> & ' hir Expr < ' hir > {
111
158
while let ExprKind :: Block ( block, _) = expr. kind {
112
159
match ( block. stmts , block. expr ) {
@@ -122,13 +169,13 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
122
169
}
123
170
124
171
/// A "wild-like" pattern is wild ("_") or `None`.
125
- /// For this lint to apply, both the outer and inner match expressions
172
+ /// For this lint to apply, both the outer and inner patterns
126
173
/// must have "wild-like" branches that can be combined.
127
- fn arm_is_wild_like ( cx : & LateContext < ' _ > , arm : & Arm < ' _ > ) -> bool {
128
- if arm . guard . is_some ( ) {
174
+ fn is_wild_like ( cx : & LateContext < ' _ > , pat_kind : & PatKind < ' _ > , arm_guard : & Option < Guard < ' _ > > ) -> bool {
175
+ if arm_guard . is_some ( ) {
129
176
return false ;
130
177
}
131
- match arm . pat . kind {
178
+ match pat_kind {
132
179
PatKind :: Binding ( ..) | PatKind :: Wild => true ,
133
180
PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
134
181
_ => false ,
0 commit comments