Skip to content

Commit de5a6d3

Browse files
committed
Initial implementation of comparison_to_empty
1 parent b06856e commit de5a6d3

File tree

9 files changed

+271
-0
lines changed

9 files changed

+271
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,7 @@ Released 2018-09-13
16641664
[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
16651665
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
16661666
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
1667+
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
16671668
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
16681669
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
16691670
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ path = "src/main.rs"
2727
name = "clippy-driver"
2828
path = "src/driver.rs"
2929

30+
[target.'cfg(NOT_A_PLATFORM)'.dependencies]
31+
rustc_data_structures = { path = "/home/urcra/rust/compiler/rustc_data_structures" }
32+
rustc_driver = { path = "/home/urcra/rust/compiler/rustc_driver" }
33+
rustc_errors = { path = "/home/urcra/rust/compiler/rustc_errors" }
34+
rustc_interface = { path = "/home/urcra/rust/compiler/rustc_interface" }
35+
rustc_middle = { path = "/home/urcra/rust/compiler/rustc_middle" }
36+
3037
[dependencies]
3138
# begin automatic update
3239
clippy_lints = { version = "0.0.212", path = "clippy_lints" }

clippy_lints/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,28 @@ license = "MIT OR Apache-2.0"
1616
keywords = ["clippy", "lint", "plugin"]
1717
edition = "2018"
1818

19+
[target.'cfg(NOT_A_PLATFORM)'.dependencies]
20+
rustc_ast = { path = "/home/urcra/rust/compiler/rustc_ast" }
21+
rustc_ast_pretty = { path = "/home/urcra/rust/compiler/rustc_ast_pretty" }
22+
rustc_attr = { path = "/home/urcra/rust/compiler/rustc_attr" }
23+
rustc_data_structures = { path = "/home/urcra/rust/compiler/rustc_data_structures" }
24+
rustc_errors = { path = "/home/urcra/rust/compiler/rustc_errors" }
25+
rustc_hir = { path = "/home/urcra/rust/compiler/rustc_hir" }
26+
rustc_hir_pretty = { path = "/home/urcra/rust/compiler/rustc_hir_pretty" }
27+
rustc_index = { path = "/home/urcra/rust/compiler/rustc_index" }
28+
rustc_infer = { path = "/home/urcra/rust/compiler/rustc_infer" }
29+
rustc_lexer = { path = "/home/urcra/rust/compiler/rustc_lexer" }
30+
rustc_lint = { path = "/home/urcra/rust/compiler/rustc_lint" }
31+
rustc_middle = { path = "/home/urcra/rust/compiler/rustc_middle" }
32+
rustc_mir = { path = "/home/urcra/rust/compiler/rustc_mir" }
33+
rustc_parse = { path = "/home/urcra/rust/compiler/rustc_parse" }
34+
rustc_parse_format = { path = "/home/urcra/rust/compiler/rustc_parse_format" }
35+
rustc_session = { path = "/home/urcra/rust/compiler/rustc_session" }
36+
rustc_span = { path = "/home/urcra/rust/compiler/rustc_span" }
37+
rustc_target = { path = "/home/urcra/rust/compiler/rustc_target" }
38+
rustc_trait_selection = { path = "/home/urcra/rust/compiler/rustc_trait_selection" }
39+
rustc_typeck = { path = "/home/urcra/rust/compiler/rustc_typeck" }
40+
1941
[dependencies]
2042
cargo_metadata = "0.12"
2143
if_chain = "1.0.0"
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
2+
use rustc_ast::ast::LitKind;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::def_id::DefId;
5+
use rustc_hir::{BinOpKind, Expr, ExprKind, ItemKind, TraitItemRef};
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_middle::ty;
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
use rustc_span::source_map::{Span, Spanned};
10+
11+
declare_clippy_lint! {
12+
/// **What it does:**
13+
///
14+
/// **Why is this bad?**
15+
///
16+
/// **Known problems:** None.
17+
///
18+
/// **Example:**
19+
///
20+
/// ```rust
21+
/// // example code where clippy issues a warning
22+
/// ```
23+
/// Use instead:
24+
/// ```rust
25+
/// // example code which does not raise clippy warning
26+
/// ```
27+
pub COMPARISON_TO_EMPTY,
28+
style,
29+
"default lint description"
30+
}
31+
32+
declare_lint_pass!(ComparisonToEmpty => [COMPARISON_TO_EMPTY]);
33+
34+
impl LateLintPass<'_> for ComparisonToEmpty {
35+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
36+
if expr.span.from_expansion() {
37+
return;
38+
}
39+
40+
if let ExprKind::Binary(Spanned { node: cmp, .. }, ref left, ref right) = expr.kind {
41+
match cmp {
42+
BinOpKind::Eq => {
43+
check_cmp(cx, expr.span, left, right, "", 0); // len == 0
44+
check_cmp(cx, expr.span, right, left, "", 0); // 0 == len
45+
},
46+
BinOpKind::Ne => {
47+
check_cmp(cx, expr.span, left, right, "!", 0); // len != 0
48+
check_cmp(cx, expr.span, right, left, "!", 0); // 0 != len
49+
},
50+
BinOpKind::Gt => {
51+
check_cmp(cx, expr.span, left, right, "!", 0); // len > 0
52+
check_cmp(cx, expr.span, right, left, "", 1); // 1 > len
53+
},
54+
BinOpKind::Lt => {
55+
check_cmp(cx, expr.span, left, right, "", 1); // len < 1
56+
check_cmp(cx, expr.span, right, left, "!", 0); // 0 < len
57+
},
58+
BinOpKind::Ge => check_cmp(cx, expr.span, left, right, "!", 1), // len >= 1
59+
BinOpKind::Le => check_cmp(cx, expr.span, right, left, "!", 1), // 1 <= len
60+
_ => (),
61+
}
62+
}
63+
}
64+
65+
}
66+
67+
68+
fn check_cmp(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str, compare_to: u32) {
69+
check_empty_expr(cx, span, lit1, lit2, op)
70+
}
71+
72+
fn check_empty_expr(
73+
cx: &LateContext<'_>,
74+
span: Span,
75+
lit1: &Expr<'_>,
76+
lit2: &Expr<'_>,
77+
op: &str
78+
) {
79+
if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) {
80+
let mut applicability = Applicability::MachineApplicable;
81+
span_lint_and_sugg(
82+
cx,
83+
COMPARISON_TO_EMPTY,
84+
span,
85+
&format!("comparison to empty slice"),
86+
&format!("using `{}is_empty` is clearer and more explicit", op),
87+
format!(
88+
"{}{}.is_empty()",
89+
op,
90+
snippet_with_applicability(cx, lit1.span, "_", &mut applicability)
91+
),
92+
applicability,
93+
);
94+
}
95+
}
96+
97+
fn is_empty_string(expr: &Expr<'_>) -> bool {
98+
if let ExprKind::Lit(ref lit) = expr.kind {
99+
if let LitKind::Str(lit, _) = lit.node {
100+
let lit = lit.as_str();
101+
return lit == "";
102+
}
103+
}
104+
false
105+
}
106+
107+
fn is_empty_array(expr: &Expr<'_>) -> bool {
108+
if let ExprKind::Array(ref arr) = expr.kind {
109+
return arr.is_empty();
110+
}
111+
false
112+
}
113+
114+
115+
/// Checks if this type has an `is_empty` method.
116+
fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
117+
/// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
118+
fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
119+
if let ty::AssocKind::Fn = item.kind {
120+
if item.ident.name.as_str() == "is_empty" {
121+
let sig = cx.tcx.fn_sig(item.def_id);
122+
let ty = sig.skip_binder();
123+
ty.inputs().len() == 1
124+
} else {
125+
false
126+
}
127+
} else {
128+
false
129+
}
130+
}
131+
132+
/// Checks the inherent impl's items for an `is_empty(self)` method.
133+
fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
134+
cx.tcx.inherent_impls(id).iter().any(|imp| {
135+
cx.tcx
136+
.associated_items(*imp)
137+
.in_definition_order()
138+
.any(|item| is_is_empty(cx, &item))
139+
})
140+
}
141+
142+
let ty = &cx.typeck_results().expr_ty(expr).peel_refs();
143+
match ty.kind() {
144+
ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| {
145+
cx.tcx
146+
.associated_items(principal.def_id())
147+
.in_definition_order()
148+
.any(|item| is_is_empty(cx, &item))
149+
}),
150+
ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
151+
ty::Adt(id, _) => has_is_empty_impl(cx, id.did),
152+
ty::Array(..) | ty::Slice(..) | ty::Str => true,
153+
_ => false,
154+
}
155+
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ mod checked_conversions;
171171
mod cognitive_complexity;
172172
mod collapsible_if;
173173
mod comparison_chain;
174+
mod comparison_to_empty;
174175
mod copies;
175176
mod copy_iterator;
176177
mod create_dir;
@@ -523,6 +524,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
523524
&cognitive_complexity::COGNITIVE_COMPLEXITY,
524525
&collapsible_if::COLLAPSIBLE_IF,
525526
&comparison_chain::COMPARISON_CHAIN,
527+
&comparison_to_empty::COMPARISON_TO_EMPTY,
526528
&copies::IFS_SAME_COND,
527529
&copies::IF_SAME_THEN_ELSE,
528530
&copies::MATCH_SAME_ARMS,
@@ -1139,6 +1141,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11391141
store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods));
11401142
store.register_early_pass(|| box asm_syntax::InlineAsmX86AttSyntax);
11411143
store.register_early_pass(|| box asm_syntax::InlineAsmX86IntelSyntax);
1144+
store.register_late_pass(|| box comparison_to_empty::ComparisonToEmpty);
11421145

11431146

11441147
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
@@ -1299,6 +1302,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12991302
LintId::of(&bytecount::NAIVE_BYTECOUNT),
13001303
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
13011304
LintId::of(&comparison_chain::COMPARISON_CHAIN),
1305+
LintId::of(&comparison_to_empty::COMPARISON_TO_EMPTY),
13021306
LintId::of(&copies::IFS_SAME_COND),
13031307
LintId::of(&copies::IF_SAME_THEN_ELSE),
13041308
LintId::of(&derive::DERIVE_HASH_XOR_EQ),
@@ -1555,6 +1559,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15551559
LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
15561560
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
15571561
LintId::of(&comparison_chain::COMPARISON_CHAIN),
1562+
LintId::of(&comparison_to_empty::COMPARISON_TO_EMPTY),
15581563
LintId::of(&doc::MISSING_SAFETY_DOC),
15591564
LintId::of(&doc::NEEDLESS_DOCTEST_MAIN),
15601565
LintId::of(&enum_variants::ENUM_VARIANT_NAMES),

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,13 @@ vec![
291291
deprecation: None,
292292
module: "comparison_chain",
293293
},
294+
Lint {
295+
name: "comparison_to_empty",
296+
group: "style",
297+
desc: "default lint description",
298+
deprecation: None,
299+
module: "comparison_to_empty",
300+
},
294301
Lint {
295302
name: "copy_iterator",
296303
group: "pedantic",

tests/ui/comparison_to_empty.fixed

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::comparison_to_empty)]
4+
5+
fn main() {
6+
// Disallow comparisons to empty
7+
let s = String::new();
8+
let _ = s.is_empty();
9+
let _ = !s.is_empty();
10+
11+
let v = vec![0];
12+
let _ = v.is_empty();
13+
let _ = !v.is_empty();
14+
15+
// Allow comparisons to non-empty
16+
let s = String::new();
17+
let _ = s == " ";
18+
let _ = s != " ";
19+
20+
let v = vec![0];
21+
let _ = v == [0];
22+
let _ = v != [0];
23+
}

tests/ui/comparison_to_empty.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::comparison_to_empty)]
4+
5+
fn main() {
6+
// Disallow comparisons to empty
7+
let s = String::new();
8+
let _ = s == "";
9+
let _ = s != "";
10+
11+
let v = vec![0];
12+
let _ = v == [];
13+
let _ = v != [];
14+
15+
// Allow comparisons to non-empty
16+
let s = String::new();
17+
let _ = s == " ";
18+
let _ = s != " ";
19+
20+
let v = vec![0];
21+
let _ = v == [0];
22+
let _ = v != [0];
23+
}

tests/ui/comparison_to_empty.stderr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: comparison to empty slice
2+
--> $DIR/comparison_to_empty.rs:8:13
3+
|
4+
LL | let _ = s == "";
5+
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
6+
|
7+
= note: `-D clippy::comparison-to-empty` implied by `-D warnings`
8+
9+
error: comparison to empty slice
10+
--> $DIR/comparison_to_empty.rs:9:13
11+
|
12+
LL | let _ = s != "";
13+
| ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()`
14+
15+
error: comparison to empty slice
16+
--> $DIR/comparison_to_empty.rs:12:13
17+
|
18+
LL | let _ = v == [];
19+
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()`
20+
21+
error: comparison to empty slice
22+
--> $DIR/comparison_to_empty.rs:13:13
23+
|
24+
LL | let _ = v != [];
25+
| ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()`
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)