Skip to content

Commit 898b6a0

Browse files
committed
Add lint suspicious_splitn
1 parent 543a8a6 commit 898b6a0

File tree

9 files changed

+172
-7
lines changed

9 files changed

+172
-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: 35 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,35 @@ declare_clippy_lint! {
16331634
"replace `.iter().count()` with `.len()`"
16341635
}
16351636

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

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

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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
}

tests/ui/suspicious_splitn.stderr

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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: aborting due to 7 previous errors
59+

0 commit comments

Comments
 (0)