11use clippy_utils:: diagnostics:: span_lint_and_then;
22use 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 } ;
44use if_chain:: if_chain;
55use 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 } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
99use rustc_span:: { MultiSpan , Span } ;
@@ -49,104 +49,87 @@ declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
4949
5050impl < ' tcx > LateLintPass < ' tcx > for CollapsibleMatch {
5151 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+ }
6858 }
6959 }
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) ;
7362 }
63+ None => { }
7464 }
7565 }
7666}
7767
7868fn check_arm < ' tcx > (
7969 cx : & LateContext < ' tcx > ,
80- outer_block : & ' tcx Expr < ' tcx > ,
81- outer_guard : Option < & Guard < ' tcx > > ,
70+ outer_is_match : bool ,
8271 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 > >
8475) {
85- let expr = strip_singleton_blocks ( outer_block ) ;
76+ let inner_expr = strip_singleton_blocks ( outer_then_body ) ;
8677 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( ) ;
9495 // match expression must be a local binding
9596 // 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) ;
10299 // the binding must come from the pattern of the containing match arm
103100 // ..<local>.. => match <local> { .. }
104101 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+ } ;
107108 // the binding must not be used in the if guard
108109 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) ) ,
112117 } ;
113- // ...or anywhere in the inner match
114- if !arms_inner. iter( ) . any( |arm| used_visitor. check_arm( arm) ) ;
115118 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" } ,
127123 ) ;
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 {
141124 span_lint_and_then(
142125 cx,
143126 COLLAPSIBLE_MATCH ,
144- block_inner . span,
145- "unnecessary nested `if let` or `match`" ,
127+ inner_expr . span,
128+ & msg ,
146129 |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] ) ;
148131 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( ) ) ;
150133 diag. span_help( help_span, "the outer pattern can be modified to include the inner pattern" ) ;
151134 } ,
152135 ) ;
@@ -168,14 +151,30 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
168151 expr
169152}
170153
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 ( ) {
176175 return false ;
177176 }
178- match pat_kind {
177+ match arm . pat . kind {
179178 PatKind :: Binding ( ..) | PatKind :: Wild => true ,
180179 PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
181180 _ => false ,
0 commit comments