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