|
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