Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 37 additions & 32 deletions clippy_lints/src/equatable_if_let.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
Expand Down Expand Up @@ -40,9 +40,9 @@ declare_clippy_lint! {
declare_lint_pass!(PatternEquality => [EQUATABLE_IF_LET]);

/// detects if pattern matches just one thing
fn unary_pattern(pat: &Pat<'_>) -> bool {
fn is_unary_pattern(pat: &Pat<'_>) -> bool {
fn array_rec(pats: &[Pat<'_>]) -> bool {
pats.iter().all(unary_pattern)
pats.iter().all(is_unary_pattern)
}
match &pat.kind {
PatKind::Missing => unreachable!(),
Expand All @@ -53,9 +53,9 @@ fn unary_pattern(pat: &Pat<'_>) -> bool {
| PatKind::Never
| PatKind::Or(_)
| PatKind::Err(_) => false,
PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| unary_pattern(x.pat)),
PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| is_unary_pattern(x.pat)),
PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a),
PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x),
PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => is_unary_pattern(x),
PatKind::Expr(_) => true,
}
}
Expand Down Expand Up @@ -103,48 +103,53 @@ fn contains_type_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
impl<'tcx> LateLintPass<'tcx> for PatternEquality {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Let(let_expr) = expr.kind
&& unary_pattern(let_expr.pat)
&& !expr.span.in_external_macro(cx.sess().source_map())
&& is_unary_pattern(let_expr.pat)
&& !let_expr.pat.span.from_expansion()
&& !let_expr.init.span.from_expansion()
{
let exp_ty = cx.typeck_results().expr_ty(let_expr.init);
let pat_ty = cx.typeck_results().pat_ty(let_expr.pat);
let mut applicability = Applicability::MachineApplicable;

let mut app = Applicability::MachineApplicable;
let ctxt = expr.span.ctxt();

if is_structural_partial_eq(cx, exp_ty, pat_ty) && !contains_type_mismatch(cx, let_expr.pat) {
let pat_str = match let_expr.pat.kind {
PatKind::Struct(..) => format!(
"({})",
snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
),
_ => snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability)
.0
.to_string(),
};
span_lint_and_sugg(
span_lint_and_then(
cx,
EQUATABLE_IF_LET,
expr.span,
"this pattern matching can be expressed using equality",
"try",
format!(
"{} == {pat_str}",
snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
),
applicability,
|diag| {
let pat_str = {
let str = snippet_with_context(cx, let_expr.pat.span, ctxt, "..", &mut app).0;
if let PatKind::Struct(..) = let_expr.pat.kind {
format!("({str})").into()
} else {
str
}
};

let sugg = format!(
"{} == {pat_str}",
snippet_with_context(cx, let_expr.init.span, ctxt, "..", &mut app).0,
);
diag.span_suggestion(expr.span, "try", sugg, app);
},
);
} else {
span_lint_and_sugg(
span_lint_and_then(
cx,
EQUATABLE_IF_LET,
expr.span,
"this pattern matching can be expressed using `matches!`",
"try",
format!(
"matches!({}, {})",
snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
),
applicability,
|diag| {
let sugg = format!(
"matches!({}, {})",
snippet_with_context(cx, let_expr.init.span, ctxt, "..", &mut app).0,
snippet_with_context(cx, let_expr.pat.span, ctxt, "..", &mut app).0,
);
diag.span_suggestion(expr.span, "try", sugg, app);
},
);
}
}
Expand Down
53 changes: 37 additions & 16 deletions tests/ui/equatable_if_let.fixed
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
//@aux-build:proc_macros.rs

#![allow(
unused_variables,
dead_code,
clippy::derive_partial_eq_without_eq,
clippy::needless_if
)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::needless_if)]
#![warn(clippy::equatable_if_let)]

extern crate proc_macros;
use proc_macros::{external, inline_macros};

use std::cmp::Ordering;
Expand Down Expand Up @@ -48,7 +41,6 @@ impl PartialEq for NotStructuralEq {
}
}

#[inline_macros]
fn main() {
let a = 2;
let b = 3;
Expand Down Expand Up @@ -95,13 +87,6 @@ fn main() {
//~^ equatable_if_let
if matches!(h, NoPartialEqStruct { a: 2, b: false }) {}
//~^ equatable_if_let

if "abc" == inline!("abc") {
//~^ equatable_if_let
println!("OK");
}

external!({ if let 2 = $a {} });
}

mod issue8710 {
Expand Down Expand Up @@ -139,3 +124,39 @@ mod issue8710 {
}
}
}

#[inline_macros]
fn issue14548() {
if let inline!("abc") = "abc" {
println!("OK");
}

let a = 2;
external!({ if let 2 = $a {} });

macro_rules! generate_script {
($($($font:literal)|*),+) => {
pub fn script(font: &str) {
$(
if let $($font)|* = font {}
)*
}
}
}

generate_script! {
"Sans" | "Serif" | "Sans Mono",
"Znamenny Musical Notation"
}

macro_rules! generate_script2 {
($($font:literal)|*) => {{
pub fn script2(font: &str) {
if let $($font)|* = font {}
}
}}
}

generate_script2!("Sans" | "Serif" | "Sans Mono");
generate_script2!("Znamenny Musical Notation");
}
53 changes: 37 additions & 16 deletions tests/ui/equatable_if_let.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
//@aux-build:proc_macros.rs

#![allow(
unused_variables,
dead_code,
clippy::derive_partial_eq_without_eq,
clippy::needless_if
)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::needless_if)]
#![warn(clippy::equatable_if_let)]

extern crate proc_macros;
use proc_macros::{external, inline_macros};

use std::cmp::Ordering;
Expand Down Expand Up @@ -48,7 +41,6 @@ impl PartialEq for NotStructuralEq {
}
}

#[inline_macros]
fn main() {
let a = 2;
let b = 3;
Expand Down Expand Up @@ -95,13 +87,6 @@ fn main() {
//~^ equatable_if_let
if let NoPartialEqStruct { a: 2, b: false } = h {}
//~^ equatable_if_let

if let inline!("abc") = "abc" {
//~^ equatable_if_let
println!("OK");
}

external!({ if let 2 = $a {} });
}

mod issue8710 {
Expand Down Expand Up @@ -139,3 +124,39 @@ mod issue8710 {
}
}
}

#[inline_macros]
fn issue14548() {
if let inline!("abc") = "abc" {
println!("OK");
}

let a = 2;
external!({ if let 2 = $a {} });

macro_rules! generate_script {
($($($font:literal)|*),+) => {
pub fn script(font: &str) {
$(
if let $($font)|* = font {}
)*
}
}
}

generate_script! {
"Sans" | "Serif" | "Sans Mono",
"Znamenny Musical Notation"
}

macro_rules! generate_script2 {
($($font:literal)|*) => {{
pub fn script2(font: &str) {
if let $($font)|* = font {}
}
}}
}

generate_script2!("Sans" | "Serif" | "Sans Mono");
generate_script2!("Znamenny Musical Notation");
}
40 changes: 17 additions & 23 deletions tests/ui/equatable_if_let.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:64:8
--> tests/ui/equatable_if_let.rs:56:8
|
LL | if let 2 = a {}
| ^^^^^^^^^ help: try: `a == 2`
Expand All @@ -8,100 +8,94 @@ LL | if let 2 = a {}
= help: to override `-D warnings` add `#[allow(clippy::equatable_if_let)]`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:66:8
--> tests/ui/equatable_if_let.rs:58:8
|
LL | if let Ordering::Greater = a.cmp(&b) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.cmp(&b) == Ordering::Greater`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:68:8
--> tests/ui/equatable_if_let.rs:60:8
|
LL | if let Some(2) = c {}
| ^^^^^^^^^^^^^^^ help: try: `c == Some(2)`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:70:8
--> tests/ui/equatable_if_let.rs:62:8
|
LL | if let Struct { a: 2, b: false } = d {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `d == (Struct { a: 2, b: false })`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:72:8
--> tests/ui/equatable_if_let.rs:64:8
|
LL | if let Enum::TupleVariant(32, 64) = e {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::TupleVariant(32, 64)`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:74:8
--> tests/ui/equatable_if_let.rs:66:8
|
LL | if let Enum::RecordVariant { a: 64, b: 32 } = e {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == (Enum::RecordVariant { a: 64, b: 32 })`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:76:8
--> tests/ui/equatable_if_let.rs:68:8
|
LL | if let Enum::UnitVariant = e {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::UnitVariant`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:78:8
--> tests/ui/equatable_if_let.rs:70:8
|
LL | if let (Enum::UnitVariant, &Struct { a: 2, b: false }) = (e, &d) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(e, &d) == (Enum::UnitVariant, &Struct { a: 2, b: false })`

error: this pattern matching can be expressed using `matches!`
--> tests/ui/equatable_if_let.rs:88:8
--> tests/ui/equatable_if_let.rs:80:8
|
LL | if let NotPartialEq::A = f {}
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(f, NotPartialEq::A)`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:90:8
--> tests/ui/equatable_if_let.rs:82:8
|
LL | if let NotStructuralEq::A = g {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `g == NotStructuralEq::A`

error: this pattern matching can be expressed using `matches!`
--> tests/ui/equatable_if_let.rs:92:8
--> tests/ui/equatable_if_let.rs:84:8
|
LL | if let Some(NotPartialEq::A) = Some(f) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(Some(f), Some(NotPartialEq::A))`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:94:8
--> tests/ui/equatable_if_let.rs:86:8
|
LL | if let Some(NotStructuralEq::A) = Some(g) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(g) == Some(NotStructuralEq::A)`

error: this pattern matching can be expressed using `matches!`
--> tests/ui/equatable_if_let.rs:96:8
--> tests/ui/equatable_if_let.rs:88:8
|
LL | if let NoPartialEqStruct { a: 2, b: false } = h {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(h, NoPartialEqStruct { a: 2, b: false })`

error: this pattern matching can be expressed using equality
--> tests/ui/equatable_if_let.rs:99:8
|
LL | if let inline!("abc") = "abc" {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"abc" == inline!("abc")`

error: this pattern matching can be expressed using `matches!`
--> tests/ui/equatable_if_let.rs:109:12
--> tests/ui/equatable_if_let.rs:94:12
|
LL | if let Some('i') = cs.iter().next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(cs.iter().next(), Some('i'))`

error: this pattern matching can be expressed using `matches!`
--> tests/ui/equatable_if_let.rs:117:12
--> tests/ui/equatable_if_let.rs:102:12
|
LL | if let Some(1) = cs.iter().next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(cs.iter().next(), Some(1))`

error: this pattern matching can be expressed using `matches!`
--> tests/ui/equatable_if_let.rs:135:12
--> tests/ui/equatable_if_let.rs:120:12
|
LL | if let Some(MyEnum::B) = get_enum() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(get_enum(), Some(MyEnum::B))`

error: aborting due to 17 previous errors
error: aborting due to 16 previous errors

Loading