|
1 | 1 | use clippy_utils::diagnostics::span_lint;
|
| 2 | +use clippy_utils::higher::IfLetOrMatch; |
2 | 3 | use clippy_utils::visitors::expr_visitor_no_bodies;
|
3 |
| -use clippy_utils::{higher, meets_msrv, msrvs, peel_blocks}; |
| 4 | +use clippy_utils::{meets_msrv, msrvs, peel_blocks}; |
4 | 5 | use if_chain::if_chain;
|
5 | 6 | use rustc_hir::intravisit::Visitor;
|
6 |
| -use rustc_hir::{Expr, ExprKind, Pat, QPath, Stmt, StmtKind}; |
| 7 | +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, QPath, Stmt, StmtKind}; |
7 | 8 | use rustc_lint::{LateContext, LateLintPass, LintContext};
|
8 | 9 | use rustc_middle::lint::in_external_macro;
|
9 | 10 | use rustc_semver::RustcVersion;
|
@@ -55,29 +56,63 @@ impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
|
55 | 56 |
|
56 | 57 | impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
|
57 | 58 | fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
|
58 |
| - if !meets_msrv(self.msrv.as_ref(), &msrvs::LET_ELSE) { |
59 |
| - return; |
60 |
| - } |
61 |
| - |
62 |
| - if in_external_macro(cx.sess(), stmt.span) { |
63 |
| - return; |
64 |
| - } |
65 |
| - |
66 |
| - if_chain! { |
| 59 | + let if_let_or_match = if_chain! { |
| 60 | + if meets_msrv(self.msrv.as_ref(), &msrvs::LET_ELSE); |
| 61 | + if !in_external_macro(cx.sess(), stmt.span); |
67 | 62 | if let StmtKind::Local(local) = stmt.kind;
|
68 | 63 | if let Some(init) = local.init;
|
69 |
| - if let Some(higher::IfLet { let_pat, let_expr: _, if_then, if_else }) = higher::IfLet::hir(cx, init); |
70 |
| - if if_then_simple_identity(let_pat, if_then); |
71 |
| - if let Some(if_else) = if_else; |
72 |
| - if expr_diverges(cx, if_else); |
| 64 | + if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init); |
73 | 65 | then {
|
74 |
| - span_lint( |
75 |
| - cx, |
76 |
| - MANUAL_LET_ELSE, |
77 |
| - stmt.span, |
78 |
| - "this could be rewritten as `let else`", |
79 |
| - ); |
| 66 | + if_let_or_match |
| 67 | + } else { |
| 68 | + return; |
80 | 69 | }
|
| 70 | + }; |
| 71 | + |
| 72 | + match if_let_or_match { |
| 73 | + IfLetOrMatch::IfLet(_let_expr, let_pat, if_then, if_else) => if_chain! { |
| 74 | + if expr_is_simple_identity(let_pat, if_then); |
| 75 | + if let Some(if_else) = if_else; |
| 76 | + if expr_diverges(cx, if_else); |
| 77 | + then { |
| 78 | + span_lint( |
| 79 | + cx, |
| 80 | + MANUAL_LET_ELSE, |
| 81 | + stmt.span, |
| 82 | + "this could be rewritten as `let else`", |
| 83 | + ); |
| 84 | + } |
| 85 | + }, |
| 86 | + IfLetOrMatch::Match(_match_expr, arms, source) => { |
| 87 | + if source != MatchSource::Normal { |
| 88 | + return; |
| 89 | + } |
| 90 | + // Any other number than two arms doesn't (neccessarily) |
| 91 | + // have a trivial mapping to let else. |
| 92 | + if arms.len() != 2 { |
| 93 | + return; |
| 94 | + } |
| 95 | + // We iterate over both arms, trying to find one that is an identity, |
| 96 | + // one that diverges. Our check needs to work regardless of the order |
| 97 | + // of both arms. |
| 98 | + let mut found_identity_arm = false; |
| 99 | + let mut found_diverging_arm = false; |
| 100 | + for arm in arms { |
| 101 | + // Guards don't give us an easy mapping to let else |
| 102 | + if arm.guard.is_some() { |
| 103 | + return; |
| 104 | + } |
| 105 | + if expr_is_simple_identity(arm.pat, arm.body) { |
| 106 | + found_identity_arm = true; |
| 107 | + } else if expr_diverges(cx, arm.body) && pat_has_no_bindings(arm.pat) { |
| 108 | + found_diverging_arm = true; |
| 109 | + } |
| 110 | + } |
| 111 | + if !(found_identity_arm && found_diverging_arm) { |
| 112 | + return; |
| 113 | + } |
| 114 | + span_lint(cx, MANUAL_LET_ELSE, stmt.span, "this could be rewritten as `let else`"); |
| 115 | + }, |
81 | 116 | }
|
82 | 117 | }
|
83 | 118 |
|
@@ -143,16 +178,22 @@ fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
|
143 | 178 | does_diverge
|
144 | 179 | }
|
145 | 180 |
|
146 |
| -/// Checks if the passed `if_then` is a simple identity |
147 |
| -fn if_then_simple_identity(let_pat: &'_ Pat<'_>, if_then: &'_ Expr<'_>) -> bool { |
| 181 | +fn pat_has_no_bindings(pat: &'_ Pat<'_>) -> bool { |
| 182 | + let mut has_no_bindings = true; |
| 183 | + pat.each_binding_or_first(&mut |_, _, _, _| has_no_bindings = false); |
| 184 | + has_no_bindings |
| 185 | +} |
| 186 | + |
| 187 | +/// Checks if the passed block is a simple identity referring to bindings created by the pattern |
| 188 | +fn expr_is_simple_identity(pat: &'_ Pat<'_>, expr: &'_ Expr<'_>) -> bool { |
148 | 189 | // TODO support patterns with multiple bindings and tuples, like:
|
149 |
| - // let (foo, bar) = if let (Some(foo), bar) = g() { (foo, bar) } else { ... } |
| 190 | + // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... } |
150 | 191 | if_chain! {
|
151 |
| - if let ExprKind::Path(QPath::Resolved(_ty, path)) = &peel_blocks(if_then).kind; |
| 192 | + if let ExprKind::Path(QPath::Resolved(_ty, path)) = &peel_blocks(expr).kind; |
152 | 193 | if let [path_seg] = path.segments;
|
153 | 194 | then {
|
154 | 195 | let mut pat_bindings = Vec::new();
|
155 |
| - let_pat.each_binding(|_ann, _hir_id, _sp, ident| { |
| 196 | + pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| { |
156 | 197 | pat_bindings.push(ident);
|
157 | 198 | });
|
158 | 199 | if let [binding] = &pat_bindings[..] {
|
|
0 commit comments