[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
+[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
methods::SKIP_WHILE_NEXT,
methods::STRING_EXTEND_CHARS,
methods::SUSPICIOUS_MAP,
+ methods::SUSPICIOUS_SPLITN,
methods::UNINIT_ASSUMED_INIT,
methods::UNNECESSARY_FILTER_MAP,
methods::UNNECESSARY_FOLD,
LintId::of(methods::SKIP_WHILE_NEXT),
LintId::of(methods::STRING_EXTEND_CHARS),
LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(methods::SUSPICIOUS_SPLITN),
LintId::of(methods::UNINIT_ASSUMED_INIT),
LintId::of(methods::UNNECESSARY_FILTER_MAP),
LintId::of(methods::UNNECESSARY_FOLD),
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
LintId::of(methods::CLONE_DOUBLE_REF),
LintId::of(methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(methods::SUSPICIOUS_SPLITN),
LintId::of(methods::UNINIT_ASSUMED_INIT),
LintId::of(methods::ZST_OFFSET),
LintId::of(minmax::MIN_MAX),
mod skip_while_next;
mod string_extend_chars;
mod suspicious_map;
+mod suspicious_splitn;
mod uninit_assumed_init;
mod unnecessary_filter_map;
mod unnecessary_fold;
"replace `.iter().count()` with `.len()`"
}
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `splitn` and related functions with
+ /// either zero or one splits.
+ ///
+ /// **Why is this bad?** These calls don't actually split the value and are
+ /// likely to be intended as a different number.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Bad
+ /// let s = "";
+ /// for x in s.splitn(1, ":") {
+ /// // use x
+ /// }
+ ///
+ /// // Good
+ /// let s = "";
+ /// for x in s.splitn(2, ":") {
+ /// // use x
+ /// }
+ /// ```
+ pub SUSPICIOUS_SPLITN,
+ correctness,
+ "checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
+}
+
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
MAP_COLLECT_RESULT_UNIT,
FROM_ITER_INSTEAD_OF_COLLECT,
INSPECT_FOR_EACH,
- IMPLICIT_CLONE
+ IMPLICIT_CLONE,
+ SUSPICIOUS_SPLITN
]);
/// Extracts a method call name, args, and `Span` of the method name.
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
}
},
+ ("splitn" | "splitn_mut" | "rsplitn" | "rsplitn_mut", [count_arg, _]) => {
+ suspicious_splitn::check(cx, name, expr, recv, count_arg);
+ },
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
implicit_clone::check(cx, name, expr, recv, span);
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_note;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::source_map::Spanned;
+
+use super::SUSPICIOUS_SPLITN;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ count_arg: &Expr<'_>,
+) {
+ if_chain! {
+ // Ignore empty slice literal
+ if !matches!(self_arg.kind, ExprKind::Array([]));
+ // Ignore empty string literal
+ if !matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty());
+ if let ExprKind::Lit(count_lit) = &count_arg.kind;
+ if let LitKind::Int(count, _) = count_lit.node;
+ if count <= 1;
+ if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
+ let lang_items = cx.tcx.lang_items();
+ if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id);
+ then {
+ let (msg, note_msg) = if count == 0 {
+ (format!("`{}` called with `0` splits", method_name),
+ "the resulting iterator will always return `None`")
+ } else {
+ (format!("`{}` called with `1` split", method_name),
+ if lang_items.slice_impl() == Some(impl_id) {
+ "the resulting iterator will always return the entire slice followed by `None`"
+ } else {
+ "the resulting iterator will always return the entire string followed by `None`"
+ })
+ };
+
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_SPLITN,
+ expr.span,
+ &msg,
+ None,
+ note_msg,
+ );
+ }
+ }
+}
x.rsplit('x');
x.split_terminator('x');
x.rsplit_terminator('x');
- x.splitn(0, 'x');
- x.rsplitn(0, 'x');
+ x.splitn(2, 'x');
+ x.rsplitn(2, 'x');
x.matches('x');
x.rmatches('x');
x.match_indices('x');
x.rsplit("x");
x.split_terminator("x");
x.rsplit_terminator("x");
- x.splitn(0, "x");
- x.rsplitn(0, "x");
+ x.splitn(2, "x");
+ x.rsplitn(2, "x");
x.matches("x");
x.rmatches("x");
x.match_indices("x");
error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:28:17
|
-LL | x.splitn(0, "x");
+LL | x.splitn(2, "x");
| ^^^ help: try using a `char` instead: `'x'`
error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:29:18
|
-LL | x.rsplitn(0, "x");
+LL | x.rsplitn(2, "x");
| ^^^ help: try using a `char` instead: `'x'`
error: single-character string constant used as pattern
--- /dev/null
+#![warn(clippy::suspicious_splitn)]
+
+fn main() {
+ let _ = "a,b,c".splitn(3, ',');
+ let _ = [0, 1, 2, 1, 3].splitn(3, |&x| x == 1);
+ let _ = "".splitn(0, ',');
+ let _ = [].splitn(0, |&x: &u32| x == 1);
+
+ let _ = "a,b".splitn(0, ',');
+ let _ = "a,b".rsplitn(0, ',');
+ let _ = "a,b".splitn(1, ',');
+ let _ = [0, 1, 2].splitn(0, |&x| x == 1);
+ let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1);
+ let _ = [0, 1, 2].splitn(1, |&x| x == 1);
+ let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1);
+}
--- /dev/null
+error: `splitn` called with `0` splits
+ --> $DIR/suspicious_splitn.rs:9:13
+ |
+LL | let _ = "a,b".splitn(0, ',');
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::suspicious-splitn` implied by `-D warnings`
+ = note: the resulting iterator will always return `None`
+
+error: `rsplitn` called with `0` splits
+ --> $DIR/suspicious_splitn.rs:10:13
+ |
+LL | let _ = "a,b".rsplitn(0, ',');
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: `splitn` called with `1` split
+ --> $DIR/suspicious_splitn.rs:11:13
+ |
+LL | let _ = "a,b".splitn(1, ',');
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire string followed by `None`
+
+error: `splitn` called with `0` splits
+ --> $DIR/suspicious_splitn.rs:12:13
+ |
+LL | let _ = [0, 1, 2].splitn(0, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: `splitn_mut` called with `0` splits
+ --> $DIR/suspicious_splitn.rs:13:13
+ |
+LL | let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: `splitn` called with `1` split
+ --> $DIR/suspicious_splitn.rs:14:13
+ |
+LL | let _ = [0, 1, 2].splitn(1, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire slice followed by `None`
+
+error: `rsplitn_mut` called with `1` split
+ --> $DIR/suspicious_splitn.rs:15:13
+ |
+LL | let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire slice followed by `None`
+
+error: aborting due to 7 previous errors
+