|
1 | 1 | use clippy_utils::diagnostics::span_lint;
|
| 2 | +use clippy_utils::higher::IfLetOrMatch; |
2 | 3 | use clippy_utils::visitors::{for_each_expr, Descend};
|
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 |
| -use rustc_hir::{Expr, ExprKind, Pat, QPath, Stmt, StmtKind}; |
| 6 | +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, QPath, Stmt, StmtKind}; |
6 | 7 | use rustc_lint::{LateContext, LateLintPass, LintContext};
|
7 | 8 | use rustc_middle::lint::in_external_macro;
|
8 | 9 | use rustc_semver::RustcVersion;
|
9 | 10 | use rustc_session::{declare_tool_lint, impl_lint_pass};
|
| 11 | +use rustc_span::Span; |
10 | 12 | use std::ops::ControlFlow;
|
11 | 13 |
|
12 | 14 | declare_clippy_lint! {
|
@@ -56,29 +58,64 @@ impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
|
56 | 58 |
|
57 | 59 | impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
|
58 | 60 | fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
|
59 |
| - if !meets_msrv(self.msrv, msrvs::LET_ELSE) { |
60 |
| - return; |
61 |
| - } |
62 |
| - |
63 |
| - if in_external_macro(cx.sess(), stmt.span) { |
64 |
| - return; |
65 |
| - } |
66 |
| - |
67 |
| - if_chain! { |
| 61 | + let if_let_or_match = if_chain! { |
| 62 | + if meets_msrv(self.msrv, msrvs::LET_ELSE); |
| 63 | + if !in_external_macro(cx.sess(), stmt.span); |
68 | 64 | if let StmtKind::Local(local) = stmt.kind;
|
69 | 65 | if let Some(init) = local.init;
|
70 |
| - if let Some(higher::IfLet { let_pat, let_expr: _, if_then, if_else }) = higher::IfLet::hir(cx, init); |
71 |
| - if if_then_simple_identity(let_pat, if_then); |
72 |
| - if let Some(if_else) = if_else; |
73 |
| - if expr_diverges(cx, if_else); |
| 66 | + if !from_different_macros(init.span, stmt.span); |
| 67 | + if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init); |
74 | 68 | then {
|
75 |
| - span_lint( |
76 |
| - cx, |
77 |
| - MANUAL_LET_ELSE, |
78 |
| - stmt.span, |
79 |
| - "this could be rewritten as `let else`", |
80 |
| - ); |
| 69 | + if_let_or_match |
| 70 | + } else { |
| 71 | + return; |
81 | 72 | }
|
| 73 | + }; |
| 74 | + |
| 75 | + match if_let_or_match { |
| 76 | + IfLetOrMatch::IfLet(_let_expr, let_pat, if_then, if_else) => if_chain! { |
| 77 | + if expr_is_simple_identity(let_pat, if_then); |
| 78 | + if let Some(if_else) = if_else; |
| 79 | + if expr_diverges(cx, if_else); |
| 80 | + then { |
| 81 | + span_lint( |
| 82 | + cx, |
| 83 | + MANUAL_LET_ELSE, |
| 84 | + stmt.span, |
| 85 | + "this could be rewritten as `let else`", |
| 86 | + ); |
| 87 | + } |
| 88 | + }, |
| 89 | + IfLetOrMatch::Match(_match_expr, arms, source) => { |
| 90 | + if source != MatchSource::Normal { |
| 91 | + return; |
| 92 | + } |
| 93 | + // Any other number than two arms doesn't (neccessarily) |
| 94 | + // have a trivial mapping to let else. |
| 95 | + if arms.len() != 2 { |
| 96 | + return; |
| 97 | + } |
| 98 | + // We iterate over both arms, trying to find one that is an identity, |
| 99 | + // one that diverges. Our check needs to work regardless of the order |
| 100 | + // of both arms. |
| 101 | + let mut found_identity_arm = false; |
| 102 | + let mut found_diverging_arm = false; |
| 103 | + for arm in arms { |
| 104 | + // Guards don't give us an easy mapping to let else |
| 105 | + if arm.guard.is_some() { |
| 106 | + return; |
| 107 | + } |
| 108 | + if expr_is_simple_identity(arm.pat, arm.body) { |
| 109 | + found_identity_arm = true; |
| 110 | + } else if expr_diverges(cx, arm.body) && pat_has_no_bindings(arm.pat) { |
| 111 | + found_diverging_arm = true; |
| 112 | + } |
| 113 | + } |
| 114 | + if !(found_identity_arm && found_diverging_arm) { |
| 115 | + return; |
| 116 | + } |
| 117 | + span_lint(cx, MANUAL_LET_ELSE, stmt.span, "this could be rewritten as `let else`"); |
| 118 | + }, |
82 | 119 | }
|
83 | 120 | }
|
84 | 121 |
|
@@ -139,16 +176,39 @@ fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
|
139 | 176 | .is_some()
|
140 | 177 | }
|
141 | 178 |
|
142 |
| -/// Checks if the passed `if_then` is a simple identity |
143 |
| -fn if_then_simple_identity(let_pat: &'_ Pat<'_>, if_then: &'_ Expr<'_>) -> bool { |
| 179 | +/// Returns true if the two spans come from different macro sites, |
| 180 | +/// or one comes from an invocation and the other is not from a macro at all. |
| 181 | +fn from_different_macros(span_a: Span, span_b: Span) -> bool { |
| 182 | + // This pre-check is a speed up so that we don't build outer_expn_data unless needed. |
| 183 | + match (span_a.from_expansion(), span_b.from_expansion()) { |
| 184 | + (false, false) => return false, |
| 185 | + (true, false) | (false, true) => return true, |
| 186 | + // We need to determine if both are from the same macro |
| 187 | + (true, true) => (), |
| 188 | + } |
| 189 | + let data_for_comparison = |sp: Span| { |
| 190 | + let expn_data = sp.ctxt().outer_expn_data(); |
| 191 | + (expn_data.kind, expn_data.call_site) |
| 192 | + }; |
| 193 | + data_for_comparison(span_a) != data_for_comparison(span_b) |
| 194 | +} |
| 195 | + |
| 196 | +fn pat_has_no_bindings(pat: &'_ Pat<'_>) -> bool { |
| 197 | + let mut has_no_bindings = true; |
| 198 | + pat.each_binding_or_first(&mut |_, _, _, _| has_no_bindings = false); |
| 199 | + has_no_bindings |
| 200 | +} |
| 201 | + |
| 202 | +/// Checks if the passed block is a simple identity referring to bindings created by the pattern |
| 203 | +fn expr_is_simple_identity(pat: &'_ Pat<'_>, expr: &'_ Expr<'_>) -> bool { |
144 | 204 | // TODO support patterns with multiple bindings and tuples, like:
|
145 |
| - // let (foo, bar) = if let (Some(foo), bar) = g() { (foo, bar) } else { ... } |
| 205 | + // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... } |
146 | 206 | if_chain! {
|
147 |
| - if let ExprKind::Path(QPath::Resolved(_ty, path)) = &peel_blocks(if_then).kind; |
| 207 | + if let ExprKind::Path(QPath::Resolved(_ty, path)) = &peel_blocks(expr).kind; |
148 | 208 | if let [path_seg] = path.segments;
|
149 | 209 | then {
|
150 | 210 | let mut pat_bindings = Vec::new();
|
151 |
| - let_pat.each_binding(|_ann, _hir_id, _sp, ident| { |
| 211 | + pat.each_binding_or_first(&mut |_ann, _hir_id, _sp, ident| { |
152 | 212 | pat_bindings.push(ident);
|
153 | 213 | });
|
154 | 214 | if let [binding] = &pat_bindings[..] {
|
|
0 commit comments