Skip to content

Commit dfe1d3e

Browse files
committed
Don't lint if the let ... pattern is through a macro boundary
1 parent 2117e62 commit dfe1d3e

File tree

3 files changed

+66
-9
lines changed

3 files changed

+66
-9
lines changed

clippy_lints/src/manual_let_else.rs

+19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
99
use rustc_middle::lint::in_external_macro;
1010
use rustc_semver::RustcVersion;
1111
use rustc_session::{declare_tool_lint, impl_lint_pass};
12+
use rustc_span::Span;
1213

1314
declare_clippy_lint! {
1415
/// ### What it does
@@ -61,6 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
6162
if !in_external_macro(cx.sess(), stmt.span);
6263
if let StmtKind::Local(local) = stmt.kind;
6364
if let Some(init) = local.init;
65+
if !from_different_macros(init.span, stmt.span);
6466
if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init);
6567
then {
6668
if_let_or_match
@@ -178,6 +180,23 @@ fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
178180
does_diverge
179181
}
180182

183+
/// Returns true if the two spans come from different macro sites,
184+
/// or one comes from an invocation and the other is not from a macro at all.
185+
fn from_different_macros(span_a: Span, span_b: Span) -> bool {
186+
// This pre-check is a speed up so that we don't build outer_expn_data unless needed.
187+
match (span_a.from_expansion(), span_b.from_expansion()) {
188+
(false, false) => return false,
189+
(true, false) | (false, true) => return true,
190+
// We need to determine if both are from the same macro
191+
(true, true) => (),
192+
}
193+
let data_for_comparison = |sp: Span| {
194+
let expn_data = sp.ctxt().outer_expn_data();
195+
(expn_data.kind, expn_data.call_site)
196+
};
197+
data_for_comparison(span_a) != data_for_comparison(span_b)
198+
}
199+
181200
fn pat_has_no_bindings(pat: &'_ Pat<'_>) -> bool {
182201
let mut has_no_bindings = true;
183202
pat.each_binding_or_first(&mut |_, _, _, _| has_no_bindings = false);

tests/ui/manual_let_else.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![allow(unused_braces, unused_variables, dead_code)]
2-
#![allow(clippy::collapsible_else_if, clippy::unused_unit)]
2+
#![allow(clippy::collapsible_else_if, clippy::unused_unit, clippy::never_loop)]
33
#![warn(clippy::manual_let_else)]
44

55
fn g() -> Option<()> {
@@ -82,6 +82,19 @@ fn fire() {
8282
} else {
8383
return;
8484
};
85+
86+
// entirely inside macro lints
87+
macro_rules! create_binding_if_some {
88+
($n:ident, $e:expr) => {
89+
let $n = if let Some(v) = $e {
90+
v
91+
} else {
92+
return
93+
};
94+
};
95+
}
96+
create_binding_if_some!(w, g());
97+
8598
}
8699

87100
fn not_fire() {
@@ -147,4 +160,24 @@ fn not_fire() {
147160
};
148161
Some(v)
149162
}
150-
}
163+
164+
// Macro boundary inside let
165+
macro_rules! some_or_return {
166+
($e:expr) => {
167+
if let Some(v) = $e {
168+
v
169+
} else {
170+
return
171+
}
172+
};
173+
}
174+
let v = some_or_return!(g());
175+
176+
// Also macro boundary inside let, but inside a macro
177+
macro_rules! create_binding_if_some {
178+
($n:ident, $e:expr) => {
179+
let $n = some_or_return!($e);
180+
};
181+
}
182+
create_binding_if_some!(v, g());
183+
}

tests/ui/manual_let_else.stderr

+12-7
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,20 @@ LL | | return;
100100
LL | | };
101101
| |______^
102102

103-
error: this loop never actually loops
104-
--> $DIR/manual_let_else.rs:136:9
103+
error: this could be rewritten as `let else`
104+
--> $DIR/manual_let_else.rs:89:13
105105
|
106-
LL | / loop {
107-
LL | | break;
108-
LL | | }
109-
| |_________^
106+
LL | / let $n = if let Some(v) = $e {
107+
LL | | v
108+
LL | | } else {
109+
LL | | return
110+
LL | | };
111+
| |______________^
112+
...
113+
LL | create_binding_if_some!(w, g());
114+
| ------------------------------- in this macro invocation
110115
|
111-
= note: `#[deny(clippy::never_loop)]` on by default
116+
= note: this error originates in the macro `create_binding_if_some` (in Nightly builds, run with -Z macro-backtrace for more info)
112117

113118
error: aborting due to 12 previous errors
114119

0 commit comments

Comments
 (0)