Skip to content

Commit dbfbd0e

Browse files
committed
feat: add const_is_empty lint
1 parent aa2c94e commit dbfbd0e

File tree

6 files changed

+252
-1
lines changed

6 files changed

+252
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5109,6 +5109,7 @@ Released 2018-09-13
51095109
[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
51105110
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
51115111
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
5112+
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
51125113
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
51135114
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
51145115
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
350350
crate::methods::CLONE_ON_COPY_INFO,
351351
crate::methods::CLONE_ON_REF_PTR_INFO,
352352
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
353+
crate::methods::CONST_IS_EMPTY_INFO,
353354
crate::methods::DRAIN_COLLECT_INFO,
354355
crate::methods::ERR_EXPECT_INFO,
355356
crate::methods::EXPECT_FUN_CALL_INFO,

clippy_lints/src/methods/is_empty.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use clippy_utils::diagnostics::span_lint_and_note;
2+
use clippy_utils::expr_or_init;
3+
use rustc_ast::LitKind;
4+
use rustc_hir::{Expr, ExprKind};
5+
use rustc_lint::{LateContext, LintContext};
6+
use rustc_middle::lint::in_external_macro;
7+
use rustc_span::source_map::Spanned;
8+
9+
use super::CONST_IS_EMPTY;
10+
11+
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_>) {
12+
if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
13+
return;
14+
}
15+
let init_expr = expr_or_init(cx, receiver);
16+
if let Some(init_is_empty) = is_empty(init_expr)
17+
&& init_expr.span.eq_ctxt(receiver.span)
18+
{
19+
span_lint_and_note(
20+
cx,
21+
CONST_IS_EMPTY,
22+
expr.span,
23+
&format!("this expression always evaluates to {init_is_empty:?}"),
24+
Some(init_expr.span),
25+
"because its initialization value is constant",
26+
);
27+
}
28+
}
29+
30+
fn is_empty(expr: &'_ rustc_hir::Expr<'_>) -> Option<bool> {
31+
if let ExprKind::Lit(Spanned { node, .. }) = expr.kind {
32+
match node {
33+
LitKind::Str(sym, _) => Some(sym.is_empty()),
34+
LitKind::ByteStr(value, _) | LitKind::CStr(value, _) => Some(value.is_empty()),
35+
_ => None,
36+
}
37+
} else {
38+
None
39+
}
40+
}

clippy_lints/src/methods/mod.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ mod inefficient_to_string;
3636
mod inspect_for_each;
3737
mod into_iter_on_ref;
3838
mod is_digit_ascii_radix;
39+
mod is_empty;
3940
mod iter_cloned_collect;
4041
mod iter_count;
4142
mod iter_filter;
@@ -4041,6 +4042,31 @@ declare_clippy_lint! {
40414042
"calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`"
40424043
}
40434044

4045+
declare_clippy_lint! {
4046+
/// ### What it does
4047+
/// It identifies calls to `.is_empty()` on constant values.
4048+
///
4049+
/// ### Why is this bad?
4050+
/// String literals and constant values are known at compile time. Checking if they
4051+
/// are empty will always return the same value. This might not be the intention of
4052+
/// the expression.
4053+
///
4054+
/// ### Example
4055+
/// ```no_run
4056+
/// let value = "";
4057+
/// if value.is_empty() {
4058+
/// println!("the string is empty");
4059+
/// }
4060+
/// ```
4061+
/// Use instead:
4062+
/// ```no_run
4063+
/// println!("the string is empty");
4064+
/// ```
4065+
#[clippy::version = "1.78.0"]
4066+
pub CONST_IS_EMPTY,
4067+
suspicious,
4068+
"is_empty() called on strings known at compile time"
4069+
}
40444070
pub struct Methods {
40454071
avoid_breaking_exported_api: bool,
40464072
msrv: Msrv,
@@ -4089,6 +4115,7 @@ impl_lint_pass!(Methods => [
40894115
CLONE_ON_COPY,
40904116
CLONE_ON_REF_PTR,
40914117
COLLAPSIBLE_STR_REPLACE,
4118+
CONST_IS_EMPTY,
40924119
ITER_OVEREAGER_CLONED,
40934120
CLONED_INSTEAD_OF_COPIED,
40944121
FLAT_MAP_OPTION,
@@ -4442,7 +4469,7 @@ impl Methods {
44424469
("as_deref" | "as_deref_mut", []) => {
44434470
needless_option_as_deref::check(cx, expr, recv, name);
44444471
},
4445-
("as_bytes" | "is_empty", []) => {
4472+
("as_bytes", []) => {
44464473
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
44474474
redundant_as_str::check(cx, expr, recv, as_str_span, span);
44484475
}
@@ -4616,6 +4643,12 @@ impl Methods {
46164643
("hash", [arg]) => {
46174644
unit_hash::check(cx, expr, recv, arg);
46184645
},
4646+
("is_empty", []) => {
4647+
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
4648+
redundant_as_str::check(cx, expr, recv, as_str_span, span);
4649+
}
4650+
is_empty::check(cx, expr, recv);
4651+
},
46194652
("is_file", []) => filetype_is_file::check(cx, expr, recv),
46204653
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
46214654
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),

tests/ui/const_is_empty.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#![warn(clippy::const_is_empty)]
2+
3+
fn test_literal() {
4+
if "".is_empty() {
5+
//~^ERROR: this expression always evaluates to true
6+
}
7+
if "foobar".is_empty() {
8+
//~^ERROR: this expression always evaluates to false
9+
}
10+
}
11+
12+
fn test_byte_literal() {
13+
if b"".is_empty() {
14+
//~^ERROR: this expression always evaluates to true
15+
}
16+
if b"foobar".is_empty() {
17+
//~^ERROR: this expression always evaluates to false
18+
}
19+
}
20+
21+
fn test_no_mut() {
22+
let mut empty = "";
23+
if empty.is_empty() {
24+
// No lint because it is mutable
25+
}
26+
}
27+
28+
fn test_propagated() {
29+
let empty = "";
30+
let non_empty = "foobar";
31+
let empty2 = empty;
32+
let non_empty2 = non_empty;
33+
if empty2.is_empty() {
34+
//~^ERROR: this expression always evaluates to true
35+
}
36+
if non_empty2.is_empty() {
37+
//~^ERROR: this expression always evaluates to false
38+
}
39+
}
40+
41+
fn main() {
42+
let value = "foobar";
43+
let _ = value.is_empty();
44+
//~^ ERROR: this expression always evaluates to false
45+
let x = value;
46+
let _ = x.is_empty();
47+
//~^ ERROR: this expression always evaluates to false
48+
let _ = "".is_empty();
49+
//~^ ERROR: this expression always evaluates to true
50+
let _ = b"".is_empty();
51+
//~^ ERROR: this expression always evaluates to true
52+
}

tests/ui/const_is_empty.stderr

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
error: this expression always evaluates to true
2+
--> tests/ui/const_is_empty.rs:4:8
3+
|
4+
LL | if "".is_empty() {
5+
| ^^^^^^^^^^^^^
6+
|
7+
note: because its initialization value is constant
8+
--> tests/ui/const_is_empty.rs:4:8
9+
|
10+
LL | if "".is_empty() {
11+
| ^^
12+
= note: `-D clippy::const-is-empty` implied by `-D warnings`
13+
= help: to override `-D warnings` add `#[allow(clippy::const_is_empty)]`
14+
15+
error: this expression always evaluates to false
16+
--> tests/ui/const_is_empty.rs:7:8
17+
|
18+
LL | if "foobar".is_empty() {
19+
| ^^^^^^^^^^^^^^^^^^^
20+
|
21+
note: because its initialization value is constant
22+
--> tests/ui/const_is_empty.rs:7:8
23+
|
24+
LL | if "foobar".is_empty() {
25+
| ^^^^^^^^
26+
27+
error: this expression always evaluates to true
28+
--> tests/ui/const_is_empty.rs:13:8
29+
|
30+
LL | if b"".is_empty() {
31+
| ^^^^^^^^^^^^^^
32+
|
33+
note: because its initialization value is constant
34+
--> tests/ui/const_is_empty.rs:13:8
35+
|
36+
LL | if b"".is_empty() {
37+
| ^^^
38+
39+
error: this expression always evaluates to false
40+
--> tests/ui/const_is_empty.rs:16:8
41+
|
42+
LL | if b"foobar".is_empty() {
43+
| ^^^^^^^^^^^^^^^^^^^^
44+
|
45+
note: because its initialization value is constant
46+
--> tests/ui/const_is_empty.rs:16:8
47+
|
48+
LL | if b"foobar".is_empty() {
49+
| ^^^^^^^^^
50+
51+
error: this expression always evaluates to true
52+
--> tests/ui/const_is_empty.rs:33:8
53+
|
54+
LL | if empty2.is_empty() {
55+
| ^^^^^^^^^^^^^^^^^
56+
|
57+
note: because its initialization value is constant
58+
--> tests/ui/const_is_empty.rs:29:17
59+
|
60+
LL | let empty = "";
61+
| ^^
62+
63+
error: this expression always evaluates to false
64+
--> tests/ui/const_is_empty.rs:36:8
65+
|
66+
LL | if non_empty2.is_empty() {
67+
| ^^^^^^^^^^^^^^^^^^^^^
68+
|
69+
note: because its initialization value is constant
70+
--> tests/ui/const_is_empty.rs:30:21
71+
|
72+
LL | let non_empty = "foobar";
73+
| ^^^^^^^^
74+
75+
error: this expression always evaluates to false
76+
--> tests/ui/const_is_empty.rs:43:13
77+
|
78+
LL | let _ = value.is_empty();
79+
| ^^^^^^^^^^^^^^^^
80+
|
81+
note: because its initialization value is constant
82+
--> tests/ui/const_is_empty.rs:42:17
83+
|
84+
LL | let value = "foobar";
85+
| ^^^^^^^^
86+
87+
error: this expression always evaluates to false
88+
--> tests/ui/const_is_empty.rs:46:13
89+
|
90+
LL | let _ = x.is_empty();
91+
| ^^^^^^^^^^^^
92+
|
93+
note: because its initialization value is constant
94+
--> tests/ui/const_is_empty.rs:42:17
95+
|
96+
LL | let value = "foobar";
97+
| ^^^^^^^^
98+
99+
error: this expression always evaluates to true
100+
--> tests/ui/const_is_empty.rs:48:13
101+
|
102+
LL | let _ = "".is_empty();
103+
| ^^^^^^^^^^^^^
104+
|
105+
note: because its initialization value is constant
106+
--> tests/ui/const_is_empty.rs:48:13
107+
|
108+
LL | let _ = "".is_empty();
109+
| ^^
110+
111+
error: this expression always evaluates to true
112+
--> tests/ui/const_is_empty.rs:50:13
113+
|
114+
LL | let _ = b"".is_empty();
115+
| ^^^^^^^^^^^^^^
116+
|
117+
note: because its initialization value is constant
118+
--> tests/ui/const_is_empty.rs:50:13
119+
|
120+
LL | let _ = b"".is_empty();
121+
| ^^^
122+
123+
error: aborting due to 10 previous errors
124+

0 commit comments

Comments
 (0)