Skip to content

Commit d1308ae

Browse files
committed
Auto merge of #7292 - Jarcho:suspicious_splitn, r=flip1995
Add lint `suspicious_splitn` fixes: #7245 changelog: Add lint `suspicious_splitn`
2 parents 5cdba7d + 5fa08ea commit d1308ae

9 files changed

+197
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,6 +2671,7 @@ Released 2018-09-13
26712671
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
26722672
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
26732673
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
2674+
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
26742675
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
26752676
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
26762677
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
779779
methods::SKIP_WHILE_NEXT,
780780
methods::STRING_EXTEND_CHARS,
781781
methods::SUSPICIOUS_MAP,
782+
methods::SUSPICIOUS_SPLITN,
782783
methods::UNINIT_ASSUMED_INIT,
783784
methods::UNNECESSARY_FILTER_MAP,
784785
methods::UNNECESSARY_FOLD,
@@ -1312,6 +1313,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13121313
LintId::of(methods::SKIP_WHILE_NEXT),
13131314
LintId::of(methods::STRING_EXTEND_CHARS),
13141315
LintId::of(methods::SUSPICIOUS_MAP),
1316+
LintId::of(methods::SUSPICIOUS_SPLITN),
13151317
LintId::of(methods::UNINIT_ASSUMED_INIT),
13161318
LintId::of(methods::UNNECESSARY_FILTER_MAP),
13171319
LintId::of(methods::UNNECESSARY_FOLD),
@@ -1688,6 +1690,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16881690
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
16891691
LintId::of(methods::CLONE_DOUBLE_REF),
16901692
LintId::of(methods::ITERATOR_STEP_BY_ZERO),
1693+
LintId::of(methods::SUSPICIOUS_SPLITN),
16911694
LintId::of(methods::UNINIT_ASSUMED_INIT),
16921695
LintId::of(methods::ZST_OFFSET),
16931696
LintId::of(minmax::MIN_MAX),

clippy_lints/src/methods/mod.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ mod single_char_push_string;
4848
mod skip_while_next;
4949
mod string_extend_chars;
5050
mod suspicious_map;
51+
mod suspicious_splitn;
5152
mod uninit_assumed_init;
5253
mod unnecessary_filter_map;
5354
mod unnecessary_fold;
@@ -1633,6 +1634,36 @@ declare_clippy_lint! {
16331634
"replace `.iter().count()` with `.len()`"
16341635
}
16351636

1637+
declare_clippy_lint! {
1638+
/// **What it does:** Checks for calls to [`splitn`]
1639+
/// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and
1640+
/// related functions with either zero or one splits.
1641+
///
1642+
/// **Why is this bad?** These calls don't actually split the value and are
1643+
/// likely to be intended as a different number.
1644+
///
1645+
/// **Known problems:** None.
1646+
///
1647+
/// **Example:**
1648+
///
1649+
/// ```rust
1650+
/// // Bad
1651+
/// let s = "";
1652+
/// for x in s.splitn(1, ":") {
1653+
/// // use x
1654+
/// }
1655+
///
1656+
/// // Good
1657+
/// let s = "";
1658+
/// for x in s.splitn(2, ":") {
1659+
/// // use x
1660+
/// }
1661+
/// ```
1662+
pub SUSPICIOUS_SPLITN,
1663+
correctness,
1664+
"checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
1665+
}
1666+
16361667
pub struct Methods {
16371668
avoid_breaking_exported_api: bool,
16381669
msrv: Option<RustcVersion>,
@@ -1705,7 +1736,8 @@ impl_lint_pass!(Methods => [
17051736
MAP_COLLECT_RESULT_UNIT,
17061737
FROM_ITER_INSTEAD_OF_COLLECT,
17071738
INSPECT_FOR_EACH,
1708-
IMPLICIT_CLONE
1739+
IMPLICIT_CLONE,
1740+
SUSPICIOUS_SPLITN
17091741
]);
17101742

17111743
/// Extracts a method call name, args, and `Span` of the method name.
@@ -2024,6 +2056,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
20242056
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
20252057
}
20262058
},
2059+
("splitn" | "splitn_mut" | "rsplitn" | "rsplitn_mut", [count_arg, _]) => {
2060+
suspicious_splitn::check(cx, name, expr, recv, count_arg);
2061+
},
20272062
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
20282063
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
20292064
implicit_clone::check(cx, name, expr, recv, span);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use clippy_utils::consts::{constant, Constant};
2+
use clippy_utils::diagnostics::span_lint_and_note;
3+
use if_chain::if_chain;
4+
use rustc_ast::LitKind;
5+
use rustc_hir::{Expr, ExprKind};
6+
use rustc_lint::LateContext;
7+
use rustc_span::source_map::Spanned;
8+
9+
use super::SUSPICIOUS_SPLITN;
10+
11+
pub(super) fn check(
12+
cx: &LateContext<'_>,
13+
method_name: &str,
14+
expr: &Expr<'_>,
15+
self_arg: &Expr<'_>,
16+
count_arg: &Expr<'_>,
17+
) {
18+
if_chain! {
19+
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg);
20+
if count <= 1;
21+
if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
22+
if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
23+
let lang_items = cx.tcx.lang_items();
24+
if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id);
25+
then {
26+
// Ignore empty slice and string literals when used with a literal count.
27+
if (matches!(self_arg.kind, ExprKind::Array([]))
28+
|| matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
29+
) && matches!(count_arg.kind, ExprKind::Lit(_))
30+
{
31+
return;
32+
}
33+
34+
let (msg, note_msg) = if count == 0 {
35+
(format!("`{}` called with `0` splits", method_name),
36+
"the resulting iterator will always return `None`")
37+
} else {
38+
(format!("`{}` called with `1` split", method_name),
39+
if lang_items.slice_impl() == Some(impl_id) {
40+
"the resulting iterator will always return the entire slice followed by `None`"
41+
} else {
42+
"the resulting iterator will always return the entire string followed by `None`"
43+
})
44+
};
45+
46+
span_lint_and_note(
47+
cx,
48+
SUSPICIOUS_SPLITN,
49+
expr.span,
50+
&msg,
51+
None,
52+
note_msg,
53+
);
54+
}
55+
}
56+
}

tests/ui/single_char_pattern.fixed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ fn main() {
2525
x.rsplit('x');
2626
x.split_terminator('x');
2727
x.rsplit_terminator('x');
28-
x.splitn(0, 'x');
29-
x.rsplitn(0, 'x');
28+
x.splitn(2, 'x');
29+
x.rsplitn(2, 'x');
3030
x.matches('x');
3131
x.rmatches('x');
3232
x.match_indices('x');

tests/ui/single_char_pattern.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ fn main() {
2525
x.rsplit("x");
2626
x.split_terminator("x");
2727
x.rsplit_terminator("x");
28-
x.splitn(0, "x");
29-
x.rsplitn(0, "x");
28+
x.splitn(2, "x");
29+
x.rsplitn(2, "x");
3030
x.matches("x");
3131
x.rmatches("x");
3232
x.match_indices("x");

tests/ui/single_char_pattern.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ LL | x.rsplit_terminator("x");
7575
error: single-character string constant used as pattern
7676
--> $DIR/single_char_pattern.rs:28:17
7777
|
78-
LL | x.splitn(0, "x");
78+
LL | x.splitn(2, "x");
7979
| ^^^ help: try using a `char` instead: `'x'`
8080

8181
error: single-character string constant used as pattern
8282
--> $DIR/single_char_pattern.rs:29:18
8383
|
84-
LL | x.rsplitn(0, "x");
84+
LL | x.rsplitn(2, "x");
8585
| ^^^ help: try using a `char` instead: `'x'`
8686

8787
error: single-character string constant used as pattern

tests/ui/suspicious_splitn.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![warn(clippy::suspicious_splitn)]
2+
3+
fn main() {
4+
let _ = "a,b,c".splitn(3, ',');
5+
let _ = [0, 1, 2, 1, 3].splitn(3, |&x| x == 1);
6+
let _ = "".splitn(0, ',');
7+
let _ = [].splitn(0, |&x: &u32| x == 1);
8+
9+
let _ = "a,b".splitn(0, ',');
10+
let _ = "a,b".rsplitn(0, ',');
11+
let _ = "a,b".splitn(1, ',');
12+
let _ = [0, 1, 2].splitn(0, |&x| x == 1);
13+
let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1);
14+
let _ = [0, 1, 2].splitn(1, |&x| x == 1);
15+
let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1);
16+
17+
const X: usize = 0;
18+
let _ = "a,b".splitn(X + 1, ',');
19+
let _ = "a,b".splitn(X, ',');
20+
}

tests/ui/suspicious_splitn.stderr

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
error: `splitn` called with `0` splits
2+
--> $DIR/suspicious_splitn.rs:9:13
3+
|
4+
LL | let _ = "a,b".splitn(0, ',');
5+
| ^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::suspicious-splitn` implied by `-D warnings`
8+
= note: the resulting iterator will always return `None`
9+
10+
error: `rsplitn` called with `0` splits
11+
--> $DIR/suspicious_splitn.rs:10:13
12+
|
13+
LL | let _ = "a,b".rsplitn(0, ',');
14+
| ^^^^^^^^^^^^^^^^^^^^^
15+
|
16+
= note: the resulting iterator will always return `None`
17+
18+
error: `splitn` called with `1` split
19+
--> $DIR/suspicious_splitn.rs:11:13
20+
|
21+
LL | let _ = "a,b".splitn(1, ',');
22+
| ^^^^^^^^^^^^^^^^^^^^
23+
|
24+
= note: the resulting iterator will always return the entire string followed by `None`
25+
26+
error: `splitn` called with `0` splits
27+
--> $DIR/suspicious_splitn.rs:12:13
28+
|
29+
LL | let _ = [0, 1, 2].splitn(0, |&x| x == 1);
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31+
|
32+
= note: the resulting iterator will always return `None`
33+
34+
error: `splitn_mut` called with `0` splits
35+
--> $DIR/suspicious_splitn.rs:13:13
36+
|
37+
LL | let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1);
38+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39+
|
40+
= note: the resulting iterator will always return `None`
41+
42+
error: `splitn` called with `1` split
43+
--> $DIR/suspicious_splitn.rs:14:13
44+
|
45+
LL | let _ = [0, 1, 2].splitn(1, |&x| x == 1);
46+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
47+
|
48+
= note: the resulting iterator will always return the entire slice followed by `None`
49+
50+
error: `rsplitn_mut` called with `1` split
51+
--> $DIR/suspicious_splitn.rs:15:13
52+
|
53+
LL | let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1);
54+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55+
|
56+
= note: the resulting iterator will always return the entire slice followed by `None`
57+
58+
error: `splitn` called with `1` split
59+
--> $DIR/suspicious_splitn.rs:18:13
60+
|
61+
LL | let _ = "a,b".splitn(X + 1, ',');
62+
| ^^^^^^^^^^^^^^^^^^^^^^^^
63+
|
64+
= note: the resulting iterator will always return the entire string followed by `None`
65+
66+
error: `splitn` called with `0` splits
67+
--> $DIR/suspicious_splitn.rs:19:13
68+
|
69+
LL | let _ = "a,b".splitn(X, ',');
70+
| ^^^^^^^^^^^^^^^^^^^^
71+
|
72+
= note: the resulting iterator will always return `None`
73+
74+
error: aborting due to 9 previous errors
75+

0 commit comments

Comments
 (0)