Skip to content

Commit b81442b

Browse files
committed
Make comparison_to_empty work on if let/let chains
1 parent 407bfd4 commit b81442b

File tree

5 files changed

+184
-7
lines changed

5 files changed

+184
-7
lines changed

clippy_lints/src/len_zero.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
2+
use clippy_utils::higher::{IfOrIfLetInChain, LetChain};
23
use clippy_utils::source::snippet_with_context;
34
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators, sugg::Sugg};
45
use if_chain::if_chain;
56
use rustc_ast::ast::LitKind;
67
use rustc_errors::Applicability;
78
use rustc_hir::def_id::DefIdSet;
9+
use rustc_hir::PatKind;
810
use rustc_hir::{
911
def::Res, def_id::DefId, lang_items::LangItem, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg,
1012
GenericBound, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability, Node, PathSegment, PrimTy,
@@ -167,6 +169,39 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
167169
return;
168170
}
169171

172+
if let Some(let_chain) = LetChain::hir(expr) {
173+
for if_let in let_chain.conds.iter().filter_map(|cond| {
174+
if let IfOrIfLetInChain::IfLet(if_let) = cond {
175+
return Some(if_let);
176+
}
177+
None
178+
}) {
179+
if has_is_empty(cx, if_let.let_expr)
180+
&& match if_let.let_pat.kind {
181+
PatKind::Slice([], _, []) => true,
182+
PatKind::Lit(lit) if is_empty_string(lit) => true,
183+
_ => false,
184+
}
185+
{
186+
let mut applicability = Applicability::MachineApplicable;
187+
188+
let lit1 = peel_ref_operators(cx, if_let.let_expr);
189+
let lit_str =
190+
Sugg::hir_with_context(cx, lit1, if_let.let_span.ctxt(), "_", &mut applicability).maybe_par();
191+
192+
span_lint_and_sugg(
193+
cx,
194+
COMPARISON_TO_EMPTY,
195+
if_let.let_span,
196+
"comparison to empty slice using `if let`",
197+
"using `is_empty` is clearer and more explicit",
198+
format!("{lit_str}.is_empty()"),
199+
applicability,
200+
);
201+
}
202+
}
203+
}
204+
170205
if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind {
171206
// expr.span might contains parenthesis, see issue #10529
172207
let actual_span = left.span.with_hi(right.span.hi());

clippy_utils/src/higher.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::consts::{constant_simple, Constant};
66
use crate::ty::is_type_diagnostic_item;
77
use crate::{is_expn_of, match_def_path, paths};
8+
use hir::BinOpKind;
89
use if_chain::if_chain;
910
use rustc_ast::ast;
1011
use rustc_hir as hir;
@@ -137,6 +138,97 @@ impl<'hir> IfLet<'hir> {
137138
}
138139
}
139140

141+
/// A `let` chain, like `if true && let Some(true) = x {}`
142+
#[derive(Debug)]
143+
pub struct LetChain<'hir> {
144+
pub conds: Vec<IfOrIfLetInChain<'hir>>,
145+
pub if_then: &'hir Expr<'hir>,
146+
pub if_else: Option<&'hir Expr<'hir>>,
147+
}
148+
149+
impl<'hir> LetChain<'hir> {
150+
pub fn hir(expr: &Expr<'hir>) -> Option<Self> {
151+
if let ExprKind::If(cond, if_then, if_else) = expr.kind {
152+
let mut conds = vec![];
153+
let mut cursor = cond;
154+
while let ExprKind::Binary(binop, lhs, rhs) = cursor.kind
155+
&& let BinOpKind::And = binop.node
156+
{
157+
cursor = lhs;
158+
conds.push(IfOrIfLetInChain::hir(rhs)?);
159+
}
160+
161+
// The final lhs cannot be `&&`
162+
conds.push(IfOrIfLetInChain::hir(cursor)?);
163+
164+
return Some(Self {
165+
conds,
166+
if_then,
167+
if_else,
168+
});
169+
}
170+
171+
None
172+
}
173+
}
174+
175+
/// An `if let` or `if` expression in a let chain.
176+
#[derive(Debug)]
177+
pub enum IfOrIfLetInChain<'hir> {
178+
If(IfInChain<'hir>),
179+
IfLet(IfLetInChain<'hir>),
180+
}
181+
182+
impl<'hir> IfOrIfLetInChain<'hir> {
183+
pub fn hir(expr: &Expr<'hir>) -> Option<Self> {
184+
match expr.kind {
185+
ExprKind::DropTemps(cond) => Some(IfInChain { cond }.into()),
186+
ExprKind::Let(hir::Let {
187+
pat: let_pat,
188+
init: let_expr,
189+
span: let_span,
190+
..
191+
}) => Some(
192+
IfLetInChain {
193+
let_pat,
194+
let_expr,
195+
let_span: *let_span,
196+
}
197+
.into(),
198+
),
199+
_ => None,
200+
}
201+
}
202+
}
203+
204+
impl<'hir> From<IfInChain<'hir>> for IfOrIfLetInChain<'hir> {
205+
fn from(value: IfInChain<'hir>) -> Self {
206+
Self::If(value)
207+
}
208+
}
209+
210+
impl<'hir> From<IfLetInChain<'hir>> for IfOrIfLetInChain<'hir> {
211+
fn from(value: IfLetInChain<'hir>) -> Self {
212+
Self::IfLet(value)
213+
}
214+
}
215+
216+
/// An `if` expression in a let chain.
217+
#[derive(Debug)]
218+
pub struct IfInChain<'hir> {
219+
pub cond: &'hir Expr<'hir>,
220+
}
221+
222+
/// An `if let` expression in a let chain.
223+
#[derive(Debug)]
224+
pub struct IfLetInChain<'hir> {
225+
pub let_span: Span,
226+
/// `if let` pattern
227+
pub let_pat: &'hir Pat<'hir>,
228+
/// `if let` scrutinee
229+
pub let_expr: &'hir Expr<'hir>,
230+
}
231+
140232
/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
141233
pub enum IfLetOrMatch<'hir> {
142234
/// Any `match` expression

tests/ui/comparison_to_empty.fixed

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//@run-rustfix
22

33
#![warn(clippy::comparison_to_empty)]
4-
#![allow(clippy::useless_vec)]
4+
#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)]
5+
#![feature(let_chains)]
56

67
fn main() {
78
// Disallow comparisons to empty
@@ -12,6 +13,11 @@ fn main() {
1213
let v = vec![0];
1314
let _ = v.is_empty();
1415
let _ = !v.is_empty();
16+
if (*v).is_empty() {}
17+
let s = [0].as_slice();
18+
if s.is_empty() {}
19+
if s.is_empty() {}
20+
if s.is_empty() && s.is_empty() {}
1521

1622
// Allow comparisons to non-empty
1723
let s = String::new();
@@ -21,4 +27,8 @@ fn main() {
2127
let v = vec![0];
2228
let _ = v == [0];
2329
let _ = v != [0];
30+
if let [0] = &*v {}
31+
let s = [0].as_slice();
32+
if let [0] = s {}
33+
if let [0] = &*s && s == [0] {}
2434
}

tests/ui/comparison_to_empty.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//@run-rustfix
22

33
#![warn(clippy::comparison_to_empty)]
4-
#![allow(clippy::useless_vec)]
4+
#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)]
5+
#![feature(let_chains)]
56

67
fn main() {
78
// Disallow comparisons to empty
@@ -12,6 +13,11 @@ fn main() {
1213
let v = vec![0];
1314
let _ = v == [];
1415
let _ = v != [];
16+
if let [] = &*v {}
17+
let s = [0].as_slice();
18+
if let [] = s {}
19+
if let [] = &*s {}
20+
if let [] = &*s && s == [] {}
1521

1622
// Allow comparisons to non-empty
1723
let s = String::new();
@@ -21,4 +27,8 @@ fn main() {
2127
let v = vec![0];
2228
let _ = v == [0];
2329
let _ = v != [0];
30+
if let [0] = &*v {}
31+
let s = [0].as_slice();
32+
if let [0] = s {}
33+
if let [0] = &*s && s == [0] {}
2434
}

tests/ui/comparison_to_empty.stderr

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,58 @@
11
error: comparison to empty slice
2-
--> $DIR/comparison_to_empty.rs:9:13
2+
--> $DIR/comparison_to_empty.rs:10:13
33
|
44
LL | let _ = s == "";
55
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
66
|
77
= note: `-D clippy::comparison-to-empty` implied by `-D warnings`
88

99
error: comparison to empty slice
10-
--> $DIR/comparison_to_empty.rs:10:13
10+
--> $DIR/comparison_to_empty.rs:11:13
1111
|
1212
LL | let _ = s != "";
1313
| ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()`
1414

1515
error: comparison to empty slice
16-
--> $DIR/comparison_to_empty.rs:13:13
16+
--> $DIR/comparison_to_empty.rs:14:13
1717
|
1818
LL | let _ = v == [];
1919
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()`
2020

2121
error: comparison to empty slice
22-
--> $DIR/comparison_to_empty.rs:14:13
22+
--> $DIR/comparison_to_empty.rs:15:13
2323
|
2424
LL | let _ = v != [];
2525
| ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()`
2626

27-
error: aborting due to 4 previous errors
27+
error: comparison to empty slice using `if let`
28+
--> $DIR/comparison_to_empty.rs:16:8
29+
|
30+
LL | if let [] = &*v {}
31+
| ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(*v).is_empty()`
32+
33+
error: comparison to empty slice using `if let`
34+
--> $DIR/comparison_to_empty.rs:18:8
35+
|
36+
LL | if let [] = s {}
37+
| ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
38+
39+
error: comparison to empty slice using `if let`
40+
--> $DIR/comparison_to_empty.rs:19:8
41+
|
42+
LL | if let [] = &*s {}
43+
| ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
44+
45+
error: comparison to empty slice using `if let`
46+
--> $DIR/comparison_to_empty.rs:20:8
47+
|
48+
LL | if let [] = &*s && s == [] {}
49+
| ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
50+
51+
error: comparison to empty slice
52+
--> $DIR/comparison_to_empty.rs:20:24
53+
|
54+
LL | if let [] = &*s && s == [] {}
55+
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
56+
57+
error: aborting due to 9 previous errors
2858

0 commit comments

Comments
 (0)