Skip to content

Commit df627dd

Browse files
authored
fix(equatable_if_let): don't lint if pattern or initializer come from expansion (#15958)
This means some existing test cases won't get linted anymore, but imo that's preferable to false-positives. Fixes rust-lang/rust-clippy#14548 changelog: [`equatable_if_let`]: don't lint if pattern or initializer come from expansion
2 parents 4292841 + db4adbd commit df627dd

File tree

4 files changed

+194
-90
lines changed

4 files changed

+194
-90
lines changed

clippy_lints/src/equatable_if_let.rs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
1+
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::is_in_const_context;
33
use clippy_utils::source::snippet_with_context;
44
use clippy_utils::ty::implements_trait;
@@ -41,9 +41,9 @@ declare_clippy_lint! {
4141
declare_lint_pass!(PatternEquality => [EQUATABLE_IF_LET]);
4242

4343
/// detects if pattern matches just one thing
44-
fn unary_pattern(pat: &Pat<'_>) -> bool {
44+
fn is_unary_pattern(pat: &Pat<'_>) -> bool {
4545
fn array_rec(pats: &[Pat<'_>]) -> bool {
46-
pats.iter().all(unary_pattern)
46+
pats.iter().all(is_unary_pattern)
4747
}
4848
match &pat.kind {
4949
PatKind::Missing => unreachable!(),
@@ -54,9 +54,9 @@ fn unary_pattern(pat: &Pat<'_>) -> bool {
5454
| PatKind::Never
5555
| PatKind::Or(_)
5656
| PatKind::Err(_) => false,
57-
PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| unary_pattern(x.pat)),
57+
PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| is_unary_pattern(x.pat)),
5858
PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a),
59-
PatKind::Ref(x, _, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x),
59+
PatKind::Ref(x, _, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => is_unary_pattern(x),
6060
PatKind::Expr(_) => true,
6161
}
6262
}
@@ -104,12 +104,16 @@ fn contains_type_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
104104
impl<'tcx> LateLintPass<'tcx> for PatternEquality {
105105
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
106106
if let ExprKind::Let(let_expr) = expr.kind
107-
&& unary_pattern(let_expr.pat)
107+
&& is_unary_pattern(let_expr.pat)
108108
&& !expr.span.in_external_macro(cx.sess().source_map())
109+
&& !let_expr.pat.span.from_expansion()
110+
&& !let_expr.init.span.from_expansion()
109111
{
110112
let exp_ty = cx.typeck_results().expr_ty(let_expr.init);
111113
let pat_ty = cx.typeck_results().pat_ty(let_expr.pat);
112-
let mut applicability = Applicability::MachineApplicable;
114+
115+
let mut app = Applicability::MachineApplicable;
116+
let ctxt = expr.span.ctxt();
113117

114118
if is_structural_partial_eq(cx, exp_ty, pat_ty)
115119
&& !contains_type_mismatch(cx, let_expr.pat)
@@ -121,40 +125,42 @@ impl<'tcx> LateLintPass<'tcx> for PatternEquality {
121125
// but that didn't quite work out (see #15482), so we just reject outright in this case
122126
&& !is_in_const_context(cx)
123127
{
124-
let pat_str = match let_expr.pat.kind {
125-
PatKind::Struct(..) => format!(
126-
"({})",
127-
snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
128-
),
129-
_ => snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability)
130-
.0
131-
.to_string(),
132-
};
133-
span_lint_and_sugg(
128+
span_lint_and_then(
134129
cx,
135130
EQUATABLE_IF_LET,
136131
expr.span,
137132
"this pattern matching can be expressed using equality",
138-
"try",
139-
format!(
140-
"{} == {pat_str}",
141-
snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
142-
),
143-
applicability,
133+
|diag| {
134+
let pat_str = {
135+
let str = snippet_with_context(cx, let_expr.pat.span, ctxt, "..", &mut app).0;
136+
if let PatKind::Struct(..) = let_expr.pat.kind {
137+
format!("({str})").into()
138+
} else {
139+
str
140+
}
141+
};
142+
143+
let sugg = format!(
144+
"{} == {pat_str}",
145+
snippet_with_context(cx, let_expr.init.span, ctxt, "..", &mut app).0,
146+
);
147+
diag.span_suggestion(expr.span, "try", sugg, app);
148+
},
144149
);
145150
} else {
146-
span_lint_and_sugg(
151+
span_lint_and_then(
147152
cx,
148153
EQUATABLE_IF_LET,
149154
expr.span,
150155
"this pattern matching can be expressed using `matches!`",
151-
"try",
152-
format!(
153-
"matches!({}, {})",
154-
snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
155-
snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
156-
),
157-
applicability,
156+
|diag| {
157+
let sugg = format!(
158+
"matches!({}, {})",
159+
snippet_with_context(cx, let_expr.init.span, ctxt, "..", &mut app).0,
160+
snippet_with_context(cx, let_expr.pat.span, ctxt, "..", &mut app).0,
161+
);
162+
diag.span_suggestion(expr.span, "try", sugg, app);
163+
},
158164
);
159165
}
160166
}

tests/ui/equatable_if_let.fixed

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
//@aux-build:proc_macros.rs
2-
3-
#![allow(
4-
unused_variables,
5-
dead_code,
6-
clippy::derive_partial_eq_without_eq,
7-
clippy::needless_ifs
8-
)]
2+
#![allow(clippy::derive_partial_eq_without_eq, clippy::needless_ifs)]
93
#![warn(clippy::equatable_if_let)]
10-
11-
extern crate proc_macros;
124
use proc_macros::{external, inline_macros};
135

146
use std::cmp::Ordering;
@@ -48,7 +40,6 @@ impl PartialEq for NotStructuralEq {
4840
}
4941
}
5042

51-
#[inline_macros]
5243
fn main() {
5344
let a = 2;
5445
let b = 3;
@@ -95,13 +86,6 @@ fn main() {
9586
//~^ equatable_if_let
9687
if matches!(h, NoPartialEqStruct { a: 2, b: false }) {}
9788
//~^ equatable_if_let
98-
99-
if "abc" == inline!("abc") {
100-
//~^ equatable_if_let
101-
println!("OK");
102-
}
103-
104-
external!({ if let 2 = $a {} });
10589
}
10690

10791
mod issue8710 {
@@ -140,6 +124,74 @@ mod issue8710 {
140124
}
141125
}
142126

127+
#[inline_macros]
128+
fn issue14548() {
129+
if let inline!("abc") = "abc" {
130+
println!("OK");
131+
}
132+
133+
let a = 2;
134+
external!({ if let 2 = $a {} });
135+
136+
// Don't lint: `==`/`matches!` might be correct for a particular `$($font)|*`, but not in general
137+
macro_rules! m1 {
138+
($($font:pat_param)|*) => {
139+
if let $($font)|* = "from_expansion" {}
140+
}
141+
}
142+
m1!("foo");
143+
m1!("Sans" | "Serif" | "Sans Mono");
144+
m1!(inline!("foo"));
145+
146+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
147+
// general
148+
macro_rules! m2 {
149+
($from_root_ctxt:pat) => {
150+
if let $from_root_ctxt = "from_expansion" {}
151+
};
152+
}
153+
m2!("foo");
154+
m2!("Sans" | "Serif" | "Sans Mono");
155+
m2!(inline!("foo"));
156+
157+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
158+
// general
159+
macro_rules! m3 {
160+
($from_root_ctxt:expr) => {
161+
if let "from_expansion" = $from_root_ctxt {}
162+
};
163+
}
164+
m3!("foo");
165+
m3!("foo");
166+
m3!(inline!("foo"));
167+
168+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
169+
// general. Don't get confused by the scrutinee coming from macro invocation
170+
macro_rules! m4 {
171+
($from_root_ctxt:pat) => {
172+
if let $from_root_ctxt = inline!("from_expansion") {}
173+
};
174+
}
175+
m4!("foo");
176+
m4!("Sans" | "Serif" | "Sans Mono");
177+
m4!(inline!("foo"));
178+
179+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
180+
// general. Don't get confused by the scrutinee coming from macro invocation
181+
macro_rules! m5 {
182+
($from_root_ctxt:expr) => {
183+
if let inline!("from_expansion") = $from_root_ctxt {}
184+
};
185+
}
186+
m5!("foo");
187+
m5!("foo");
188+
m5!(inline!("foo"));
189+
190+
// Would be nice to lint: both sides are macro _invocations_, so the suggestion is correct in
191+
// general
192+
if let inline!("foo") = inline!("bar") {}
193+
}
194+
143195
// PartialEq is not stable in consts yet
144196
fn issue15376() {
145197
enum NonConstEq {

tests/ui/equatable_if_let.rs

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
//@aux-build:proc_macros.rs
2-
3-
#![allow(
4-
unused_variables,
5-
dead_code,
6-
clippy::derive_partial_eq_without_eq,
7-
clippy::needless_ifs
8-
)]
2+
#![allow(clippy::derive_partial_eq_without_eq, clippy::needless_ifs)]
93
#![warn(clippy::equatable_if_let)]
10-
11-
extern crate proc_macros;
124
use proc_macros::{external, inline_macros};
135

146
use std::cmp::Ordering;
@@ -48,7 +40,6 @@ impl PartialEq for NotStructuralEq {
4840
}
4941
}
5042

51-
#[inline_macros]
5243
fn main() {
5344
let a = 2;
5445
let b = 3;
@@ -95,13 +86,6 @@ fn main() {
9586
//~^ equatable_if_let
9687
if let NoPartialEqStruct { a: 2, b: false } = h {}
9788
//~^ equatable_if_let
98-
99-
if let inline!("abc") = "abc" {
100-
//~^ equatable_if_let
101-
println!("OK");
102-
}
103-
104-
external!({ if let 2 = $a {} });
10589
}
10690

10791
mod issue8710 {
@@ -140,6 +124,74 @@ mod issue8710 {
140124
}
141125
}
142126

127+
#[inline_macros]
128+
fn issue14548() {
129+
if let inline!("abc") = "abc" {
130+
println!("OK");
131+
}
132+
133+
let a = 2;
134+
external!({ if let 2 = $a {} });
135+
136+
// Don't lint: `==`/`matches!` might be correct for a particular `$($font)|*`, but not in general
137+
macro_rules! m1 {
138+
($($font:pat_param)|*) => {
139+
if let $($font)|* = "from_expansion" {}
140+
}
141+
}
142+
m1!("foo");
143+
m1!("Sans" | "Serif" | "Sans Mono");
144+
m1!(inline!("foo"));
145+
146+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
147+
// general
148+
macro_rules! m2 {
149+
($from_root_ctxt:pat) => {
150+
if let $from_root_ctxt = "from_expansion" {}
151+
};
152+
}
153+
m2!("foo");
154+
m2!("Sans" | "Serif" | "Sans Mono");
155+
m2!(inline!("foo"));
156+
157+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
158+
// general
159+
macro_rules! m3 {
160+
($from_root_ctxt:expr) => {
161+
if let "from_expansion" = $from_root_ctxt {}
162+
};
163+
}
164+
m3!("foo");
165+
m3!("foo");
166+
m3!(inline!("foo"));
167+
168+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
169+
// general. Don't get confused by the scrutinee coming from macro invocation
170+
macro_rules! m4 {
171+
($from_root_ctxt:pat) => {
172+
if let $from_root_ctxt = inline!("from_expansion") {}
173+
};
174+
}
175+
m4!("foo");
176+
m4!("Sans" | "Serif" | "Sans Mono");
177+
m4!(inline!("foo"));
178+
179+
// Don't lint: the suggestion might be correct for a particular `$from_root_ctxt`, but not in
180+
// general. Don't get confused by the scrutinee coming from macro invocation
181+
macro_rules! m5 {
182+
($from_root_ctxt:expr) => {
183+
if let inline!("from_expansion") = $from_root_ctxt {}
184+
};
185+
}
186+
m5!("foo");
187+
m5!("foo");
188+
m5!(inline!("foo"));
189+
190+
// Would be nice to lint: both sides are macro _invocations_, so the suggestion is correct in
191+
// general
192+
if let inline!("foo") = inline!("bar") {}
193+
}
194+
143195
// PartialEq is not stable in consts yet
144196
fn issue15376() {
145197
enum NonConstEq {

0 commit comments

Comments
 (0)