Skip to content

Commit 9be28b1

Browse files
committed
Auto merge of rust-lang#13421 - lukaslueg:issue11212, r=Manishearth
Initial impl of `unnecessary_first_then_check` Fixes rust-lang#11212 Checks for `{slice/vec/Box<[]>}.first().is_some()` and suggests replacing the unnecessary `Option`-construct with a direct `{slice/...}.is_empty()`. Other lints guide constructs like `if let Some(_) = v.get(0)` into this, which end up as `!v.is_empty()`. changelog: [`unnecessary_first_then_check`]: Initial implementation
2 parents 2e5b680 + 290a01e commit 9be28b1

7 files changed

+182
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6021,6 +6021,7 @@ Released 2018-09-13
60216021
[`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions
60226022
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
60236023
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
6024+
[`unnecessary_first_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_first_then_check
60246025
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
60256026
[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
60266027
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
467467
crate::methods::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO,
468468
crate::methods::UNNECESSARY_FILTER_MAP_INFO,
469469
crate::methods::UNNECESSARY_FIND_MAP_INFO,
470+
crate::methods::UNNECESSARY_FIRST_THEN_CHECK_INFO,
470471
crate::methods::UNNECESSARY_FOLD_INFO,
471472
crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO,
472473
crate::methods::UNNECESSARY_JOIN_INFO,

clippy_lints/src/methods/mod.rs

+33
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ mod uninit_assumed_init;
111111
mod unit_hash;
112112
mod unnecessary_fallible_conversions;
113113
mod unnecessary_filter_map;
114+
mod unnecessary_first_then_check;
114115
mod unnecessary_fold;
115116
mod unnecessary_get_then_check;
116117
mod unnecessary_iter_cloned;
@@ -4137,6 +4138,34 @@ declare_clippy_lint! {
41374138
"use of `map` returning the original item"
41384139
}
41394140

4141+
declare_clippy_lint! {
4142+
/// ### What it does
4143+
/// Checks the usage of `.first().is_some()` or `.first().is_none()` to check if a slice is
4144+
/// empty.
4145+
///
4146+
/// ### Why is this bad?
4147+
/// Using `.is_empty()` is shorter and better communicates the intention.
4148+
///
4149+
/// ### Example
4150+
/// ```no_run
4151+
/// let v = vec![1, 2, 3];
4152+
/// if v.first().is_none() {
4153+
/// // The vector is empty...
4154+
/// }
4155+
/// ```
4156+
/// Use instead:
4157+
/// ```no_run
4158+
/// let v = vec![1, 2, 3];
4159+
/// if v.is_empty() {
4160+
/// // The vector is empty...
4161+
/// }
4162+
/// ```
4163+
#[clippy::version = "1.83.0"]
4164+
pub UNNECESSARY_FIRST_THEN_CHECK,
4165+
complexity,
4166+
"calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`"
4167+
}
4168+
41404169
pub struct Methods {
41414170
avoid_breaking_exported_api: bool,
41424171
msrv: Msrv,
@@ -4294,6 +4323,7 @@ impl_lint_pass!(Methods => [
42944323
UNNECESSARY_RESULT_MAP_OR_ELSE,
42954324
MANUAL_C_STR_LITERALS,
42964325
UNNECESSARY_GET_THEN_CHECK,
4326+
UNNECESSARY_FIRST_THEN_CHECK,
42974327
NEEDLESS_CHARACTER_ITERATION,
42984328
MANUAL_INSPECT,
42994329
UNNECESSARY_MIN_OR_MAX,
@@ -5066,6 +5096,9 @@ fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>,
50665096
Some(("get", f_recv, [arg], _, _)) => {
50675097
unnecessary_get_then_check::check(cx, call_span, recv, f_recv, arg, is_some);
50685098
},
5099+
Some(("first", f_recv, [], _, _)) => {
5100+
unnecessary_first_then_check::check(cx, call_span, recv, f_recv, is_some);
5101+
},
50695102
_ => {},
50705103
}
50715104
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::SpanRangeExt;
3+
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Expr, ExprKind};
6+
use rustc_lint::LateContext;
7+
use rustc_span::Span;
8+
9+
use super::UNNECESSARY_FIRST_THEN_CHECK;
10+
11+
pub(super) fn check(
12+
cx: &LateContext<'_>,
13+
call_span: Span,
14+
first_call: &Expr<'_>,
15+
first_caller: &Expr<'_>,
16+
is_some: bool,
17+
) {
18+
if !cx
19+
.typeck_results()
20+
.expr_ty_adjusted(first_caller)
21+
.peel_refs()
22+
.is_slice()
23+
{
24+
return;
25+
}
26+
27+
let ExprKind::MethodCall(_, _, _, first_call_span) = first_call.kind else {
28+
return;
29+
};
30+
31+
let both_calls_span = first_call_span.with_hi(call_span.hi());
32+
if let Some(both_calls_snippet) = both_calls_span.get_source_text(cx)
33+
&& let Some(first_caller_snippet) = first_caller.span.get_source_text(cx)
34+
{
35+
let (sugg_span, suggestion) = if is_some {
36+
(
37+
first_caller.span.with_hi(call_span.hi()),
38+
format!("!{first_caller_snippet}.is_empty()"),
39+
)
40+
} else {
41+
(both_calls_span, "is_empty()".to_owned())
42+
};
43+
span_lint_and_sugg(
44+
cx,
45+
UNNECESSARY_FIRST_THEN_CHECK,
46+
sugg_span,
47+
format!(
48+
"unnecessary use of `{both_calls_snippet}` to check if slice {}",
49+
if is_some { "is not empty" } else { "is empty" }
50+
),
51+
"replace this with",
52+
suggestion,
53+
Applicability::MaybeIncorrect,
54+
);
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![warn(clippy::unnecessary_first_then_check)]
2+
#![allow(clippy::useless_vec, clippy::const_is_empty)]
3+
4+
fn main() {
5+
let s = [1, 2, 3];
6+
let _: bool = !s.is_empty();
7+
let _: bool = s.is_empty();
8+
9+
let v = vec![1, 2, 3];
10+
let _: bool = !v.is_empty();
11+
12+
let n = [[1, 2, 3], [4, 5, 6]];
13+
let _: bool = !n[0].is_empty();
14+
let _: bool = n[0].is_empty();
15+
16+
struct Foo {
17+
bar: &'static [i32],
18+
}
19+
let f = [Foo { bar: &[] }];
20+
let _: bool = !f[0].bar.is_empty();
21+
let _: bool = f[0].bar.is_empty();
22+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![warn(clippy::unnecessary_first_then_check)]
2+
#![allow(clippy::useless_vec, clippy::const_is_empty)]
3+
4+
fn main() {
5+
let s = [1, 2, 3];
6+
let _: bool = s.first().is_some();
7+
let _: bool = s.first().is_none();
8+
9+
let v = vec![1, 2, 3];
10+
let _: bool = v.first().is_some();
11+
12+
let n = [[1, 2, 3], [4, 5, 6]];
13+
let _: bool = n[0].first().is_some();
14+
let _: bool = n[0].first().is_none();
15+
16+
struct Foo {
17+
bar: &'static [i32],
18+
}
19+
let f = [Foo { bar: &[] }];
20+
let _: bool = f[0].bar.first().is_some();
21+
let _: bool = f[0].bar.first().is_none();
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error: unnecessary use of `first().is_some()` to check if slice is not empty
2+
--> tests/ui/unnecessary_first_then_check.rs:6:19
3+
|
4+
LL | let _: bool = s.first().is_some();
5+
| ^^^^^^^^^^^^^^^^^^^ help: replace this with: `!s.is_empty()`
6+
|
7+
= note: `-D clippy::unnecessary-first-then-check` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_first_then_check)]`
9+
10+
error: unnecessary use of `first().is_none()` to check if slice is empty
11+
--> tests/ui/unnecessary_first_then_check.rs:7:21
12+
|
13+
LL | let _: bool = s.first().is_none();
14+
| ^^^^^^^^^^^^^^^^^ help: replace this with: `is_empty()`
15+
16+
error: unnecessary use of `first().is_some()` to check if slice is not empty
17+
--> tests/ui/unnecessary_first_then_check.rs:10:19
18+
|
19+
LL | let _: bool = v.first().is_some();
20+
| ^^^^^^^^^^^^^^^^^^^ help: replace this with: `!v.is_empty()`
21+
22+
error: unnecessary use of `first().is_some()` to check if slice is not empty
23+
--> tests/ui/unnecessary_first_then_check.rs:13:19
24+
|
25+
LL | let _: bool = n[0].first().is_some();
26+
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace this with: `!n[0].is_empty()`
27+
28+
error: unnecessary use of `first().is_none()` to check if slice is empty
29+
--> tests/ui/unnecessary_first_then_check.rs:14:24
30+
|
31+
LL | let _: bool = n[0].first().is_none();
32+
| ^^^^^^^^^^^^^^^^^ help: replace this with: `is_empty()`
33+
34+
error: unnecessary use of `first().is_some()` to check if slice is not empty
35+
--> tests/ui/unnecessary_first_then_check.rs:20:19
36+
|
37+
LL | let _: bool = f[0].bar.first().is_some();
38+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace this with: `!f[0].bar.is_empty()`
39+
40+
error: unnecessary use of `first().is_none()` to check if slice is empty
41+
--> tests/ui/unnecessary_first_then_check.rs:21:28
42+
|
43+
LL | let _: bool = f[0].bar.first().is_none();
44+
| ^^^^^^^^^^^^^^^^^ help: replace this with: `is_empty()`
45+
46+
error: aborting due to 7 previous errors
47+

0 commit comments

Comments
 (0)