|
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