11use clippy_utils:: diagnostics:: span_lint_and_then;
22use 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 } ;
44use if_chain:: if_chain;
55use 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 } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
99use rustc_span:: { MultiSpan , Span } ;
@@ -49,22 +49,44 @@ 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+
5264 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 ) ) {
5466 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 ) ) ;
5668 }
5769 }
70+
71+ if let Some ( first_arm) = arms. get ( 0 ) {
72+ check_if_let ( cx, & first_arm. body , & first_arm. pat ) ;
73+ }
5874 }
5975 }
6076}
6177
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) ;
6486 if_chain ! {
6587 if let ExprKind :: Match ( expr_in, arms_inner, _) = expr. kind;
6688 // 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( ) ;
6890 // there must be no more than two arms in the inner match for this lint
6991 if arms_inner. len( ) == 2 ;
7092 // 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
7395 // match <local> { .. }
7496 if let Some ( binding_id) = path_to_local( peel_ref_operators( cx, expr_in) ) ;
7597 // 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 ) ) ;
7799 let ( wild_inner_arm, non_wild_inner_arm) =
78100 ( & arms_inner[ wild_inner_arm_idx] , & arms_inner[ 1 - wild_inner_arm_idx] ) ;
79101 if !pat_contains_or( non_wild_inner_arm. pat) ;
80102 // the binding must come from the pattern of the containing match arm
81103 // ..<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) ;
83105 // 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 ) ;
85107 // the binding must not be used in the if guard
86108 let mut used_visitor = LocalUsedVisitor :: new( cx, binding_id) ;
87- if match arm . guard {
109+ if match outer_guard {
88110 None => true ,
89111 Some ( Guard :: If ( expr) | Guard :: IfLet ( _, expr) ) => !used_visitor. check_expr( expr) ,
90112 } ;
@@ -107,6 +129,31 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
107129 }
108130}
109131
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+
110157fn strip_singleton_blocks < ' hir > ( mut expr : & ' hir Expr < ' hir > ) -> & ' hir Expr < ' hir > {
111158 while let ExprKind :: Block ( block, _) = expr. kind {
112159 match ( block. stmts , block. expr ) {
@@ -122,13 +169,13 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
122169}
123170
124171/// 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
126173/// 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 ( ) {
129176 return false ;
130177 }
131- match arm . pat . kind {
178+ match pat_kind {
132179 PatKind :: Binding ( ..) | PatKind :: Wild => true ,
133180 PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
134181 _ => false ,
0 commit comments