1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: visitors:: LocalUsedVisitor ;
3
- use clippy_utils:: { higher, is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq } ;
3
+ use clippy_utils:: { higher, is_lang_ctor, is_unit_expr , 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:: { Expr , ExprKind , Guard , HirId , Pat , PatKind , StmtKind } ;
6
+ use rustc_hir:: { Arm , Expr , ExprKind , Guard , HirId , MatchSource , 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,104 +49,87 @@ 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
-
64
- if let ExprKind :: Match ( _expr, arms, _source) = expr. kind {
65
- if let Some ( wild_arm) = arms. iter ( ) . rfind ( |arm| is_wild_like ( cx, & arm. pat . kind , & arm. guard ) ) {
66
- for arm in arms {
67
- check_arm ( cx, arm. body , arm. guard . as_ref ( ) , arm. pat , Some ( wild_arm. body ) ) ;
52
+ match IfLetOrMatch :: parse ( cx, expr) {
53
+ Some ( IfLetOrMatch :: Match ( _, arms, _) ) => {
54
+ if let Some ( els_arm) = arms. iter ( ) . rfind ( |arm| arm_is_wild_like ( cx, arm) ) {
55
+ for arm in arms {
56
+ check_arm ( cx, true , arm. pat , arm. body , arm. guard . as_ref ( ) , Some ( els_arm. body ) ) ;
57
+ }
68
58
}
69
59
}
70
-
71
- if let Some ( first_arm) = arms. get ( 0 ) {
72
- check_if_let ( cx, & first_arm. body , & first_arm. pat ) ;
60
+ Some ( IfLetOrMatch :: IfLet ( _, pat, body, els) ) => {
61
+ check_arm ( cx, false , pat, body, None , els) ;
73
62
}
63
+ None => { }
74
64
}
75
65
}
76
66
}
77
67
78
68
fn check_arm < ' tcx > (
79
69
cx : & LateContext < ' tcx > ,
80
- outer_block : & ' tcx Expr < ' tcx > ,
81
- outer_guard : Option < & Guard < ' tcx > > ,
70
+ outer_is_match : bool ,
82
71
outer_pat : & ' tcx Pat < ' tcx > ,
83
- wild_outer_block : Option < & ' tcx Expr < ' tcx > > ,
72
+ outer_then_body : & ' tcx Expr < ' tcx > ,
73
+ outer_guard : Option < & ' tcx Guard < ' tcx > > ,
74
+ outer_else_body : Option < & ' tcx Expr < ' tcx > >
84
75
) {
85
- let expr = strip_singleton_blocks ( outer_block ) ;
76
+ let inner_expr = strip_singleton_blocks ( outer_then_body ) ;
86
77
if_chain ! {
87
- if let ExprKind :: Match ( expr_in, arms_inner, _) = expr. kind;
88
- // the outer arm pattern and the inner match
89
- if expr_in. span. ctxt( ) == outer_pat. span. ctxt( ) ;
90
- // there must be no more than two arms in the inner match for this lint
91
- if arms_inner. len( ) == 2 ;
92
- // no if guards on the inner match
93
- if arms_inner. iter( ) . all( |arm| arm. guard. is_none( ) ) ;
78
+ if let Some ( inner) = IfLetOrMatch :: parse( cx, inner_expr) ;
79
+ if let Some ( ( inner_scrutinee, inner_then_pat, inner_else_body) ) = match inner {
80
+ IfLetOrMatch :: IfLet ( scrutinee, pat, _, els) => Some ( ( scrutinee, pat, els) ) ,
81
+ IfLetOrMatch :: Match ( scrutinee, arms, ..) => if_chain! {
82
+ // if there are more than two arms, collapsing would be non-trivial
83
+ if arms. len( ) == 2 && arms. iter( ) . all( |a| a. guard. is_none( ) ) ;
84
+ // one of the arms must be "wild-like"
85
+ if let Some ( wild_idx) = arms. iter( ) . rposition( |a| arm_is_wild_like( cx, a) ) ;
86
+ then {
87
+ let ( then, els) = ( & arms[ 1 - wild_idx] , & arms[ wild_idx] ) ;
88
+ Some ( ( scrutinee, then. pat, Some ( els. body) ) )
89
+ } else {
90
+ None
91
+ }
92
+ } ,
93
+ } ;
94
+ if outer_pat. span. ctxt( ) == inner_scrutinee. span. ctxt( ) ;
94
95
// match expression must be a local binding
95
96
// match <local> { .. }
96
- if let Some ( binding_id) = path_to_local( peel_ref_operators( cx, expr_in) ) ;
97
- // one of the branches must be "wild-like"
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) ) ;
99
- let ( wild_inner_arm, non_wild_inner_arm) =
100
- ( & arms_inner[ wild_inner_arm_idx] , & arms_inner[ 1 - wild_inner_arm_idx] ) ;
101
- if !pat_contains_or( non_wild_inner_arm. pat) ;
97
+ if let Some ( binding_id) = path_to_local( peel_ref_operators( cx, inner_scrutinee) ) ;
98
+ if !pat_contains_or( inner_then_pat) ;
102
99
// the binding must come from the pattern of the containing match arm
103
100
// ..<local>.. => match <local> { .. }
104
101
if let Some ( binding_span) = find_pat_binding( outer_pat, binding_id) ;
105
- // the "wild-like" branches must be equal
106
- if wild_outer_block. map( |el| SpanlessEq :: new( cx) . eq_expr( wild_inner_arm. body, el) ) . unwrap_or( true ) ;
102
+ // the "else" branches must be equal
103
+ if match ( outer_else_body, inner_else_body) {
104
+ ( None , None ) => true ,
105
+ ( None , Some ( e) ) | ( Some ( e) , None ) => is_unit_expr( e) ,
106
+ ( Some ( a) , Some ( b) ) => SpanlessEq :: new( cx) . eq_expr( a, b) ,
107
+ } ;
107
108
// the binding must not be used in the if guard
108
109
let mut used_visitor = LocalUsedVisitor :: new( cx, binding_id) ;
109
- if match outer_guard {
110
- None => true ,
111
- Some ( Guard :: If ( expr) | Guard :: IfLet ( _, expr) ) => !used_visitor. check_expr( expr) ,
110
+ if outer_guard. map_or( true , |( Guard :: If ( e) | Guard :: IfLet ( _, e) ) | !used_visitor. check_expr( e) ) ;
111
+ // ...or anywhere in the inner expression
112
+ if match inner {
113
+ IfLetOrMatch :: IfLet ( _, _, body, els) => {
114
+ !used_visitor. check_expr( body) && els. map_or( true , |e| !used_visitor. check_expr( e) )
115
+ } ,
116
+ IfLetOrMatch :: Match ( _, arms, ..) => !arms. iter( ) . any( |arm| used_visitor. check_arm( arm) ) ,
112
117
} ;
113
- // ...or anywhere in the inner match
114
- if !arms_inner. iter( ) . any( |arm| used_visitor. check_arm( arm) ) ;
115
118
then {
116
- span_lint_and_then(
117
- cx,
118
- COLLAPSIBLE_MATCH ,
119
- expr. span,
120
- "unnecessary nested match" ,
121
- |diag| {
122
- let mut help_span = MultiSpan :: from_spans( vec![ binding_span, non_wild_inner_arm. pat. span] ) ;
123
- help_span. push_span_label( binding_span, "replace this binding" . into( ) ) ;
124
- help_span. push_span_label( non_wild_inner_arm. pat. span, "with this pattern" . into( ) ) ;
125
- diag. span_help( help_span, "the outer pattern can be modified to include the inner pattern" ) ;
126
- } ,
119
+ let msg = format!(
120
+ "this `{}` can be collapsed into the outer `{}`" ,
121
+ if matches!( inner, IfLetOrMatch :: Match ( ..) ) { "match" } else { "if let" } ,
122
+ if outer_is_match { "match" } else { "if let" } ,
127
123
) ;
128
- }
129
- }
130
- }
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
124
span_lint_and_then(
142
125
cx,
143
126
COLLAPSIBLE_MATCH ,
144
- block_inner . span,
145
- "unnecessary nested `if let` or `match`" ,
127
+ inner_expr . span,
128
+ & msg ,
146
129
|diag| {
147
- let mut help_span = MultiSpan :: from_spans( vec![ binding_span, inner_let_pat . span] ) ;
130
+ let mut help_span = MultiSpan :: from_spans( vec![ binding_span, inner_then_pat . span] ) ;
148
131
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( ) ) ;
132
+ help_span. push_span_label( inner_then_pat . span, "with this pattern" . into( ) ) ;
150
133
diag. span_help( help_span, "the outer pattern can be modified to include the inner pattern" ) ;
151
134
} ,
152
135
) ;
@@ -168,14 +151,30 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
168
151
expr
169
152
}
170
153
171
- /// A "wild-like" pattern is wild ("_") or `None`.
172
- /// For this lint to apply, both the outer and inner patterns
173
- /// must have "wild-like" branches that can be combined.
174
- fn is_wild_like ( cx : & LateContext < ' _ > , pat_kind : & PatKind < ' _ > , arm_guard : & Option < Guard < ' _ > > ) -> bool {
175
- if arm_guard. is_some ( ) {
154
+ enum IfLetOrMatch < ' hir > {
155
+ Match ( & ' hir Expr < ' hir > , & ' hir [ Arm < ' hir > ] , MatchSource ) ,
156
+ /// scrutinee, pattern, then block, else block
157
+ IfLet ( & ' hir Expr < ' hir > , & ' hir Pat < ' hir > , & ' hir Expr < ' hir > , Option < & ' hir Expr < ' hir > > ) ,
158
+ }
159
+
160
+ impl < ' hir > IfLetOrMatch < ' hir > {
161
+ fn parse ( cx : & LateContext < ' _ > , expr : & Expr < ' hir > ) -> Option < Self > {
162
+ match expr. kind {
163
+ ExprKind :: Match ( expr, arms, source) => Some ( Self :: Match ( expr, arms, source) ) ,
164
+ _ => higher:: IfLet :: hir ( cx, expr) . map ( |higher:: IfLet { let_expr, let_pat, if_then, if_else } | {
165
+ Self :: IfLet ( let_expr, let_pat, if_then, if_else)
166
+ } )
167
+ }
168
+ }
169
+ }
170
+
171
+ /// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed"
172
+ /// into a single wild arm without any significant loss in semantics or readability.
173
+ fn arm_is_wild_like ( cx : & LateContext < ' _ > , arm : & Arm < ' _ > ) -> bool {
174
+ if arm. guard . is_some ( ) {
176
175
return false ;
177
176
}
178
- match pat_kind {
177
+ match arm . pat . kind {
179
178
PatKind :: Binding ( ..) | PatKind :: Wild => true ,
180
179
PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
181
180
_ => false ,
0 commit comments