diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 3904572c6273..a6e2e0baadbb 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1635,8 +1635,9 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for calls to `splitn` and related functions with - /// either zero or one splits. + /// **What it does:** Checks for calls to [`splitn`] + /// (https://doc.rust-lang.org/std/primitive.str.html#method.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. diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints/src/methods/suspicious_splitn.rs index 43affe20dc55..a271df60572a 100644 --- a/clippy_lints/src/methods/suspicious_splitn.rs +++ b/clippy_lints/src/methods/suspicious_splitn.rs @@ -1,3 +1,4 @@ +use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint_and_note; use if_chain::if_chain; use rustc_ast::LitKind; @@ -15,18 +16,21 @@ pub(super) fn check( 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 let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg); 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 { + // Ignore empty slice and string literals when used with a literal count. + if (matches!(self_arg.kind, ExprKind::Array([])) + || matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty()) + ) && matches!(count_arg.kind, ExprKind::Lit(_)) + { + return; + } + let (msg, note_msg) = if count == 0 { (format!("`{}` called with `0` splits", method_name), "the resulting iterator will always return `None`") diff --git a/tests/ui/suspicious_splitn.rs b/tests/ui/suspicious_splitn.rs index a944aa79cfe1..a21d94cf20bb 100644 --- a/tests/ui/suspicious_splitn.rs +++ b/tests/ui/suspicious_splitn.rs @@ -13,4 +13,8 @@ fn main() { 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); + + const X: usize = 0; + let _ = "a,b".splitn(X + 1, ','); + let _ = "a,b".splitn(X, ','); } diff --git a/tests/ui/suspicious_splitn.stderr b/tests/ui/suspicious_splitn.stderr index 2e4bfe93b944..b6220ae23933 100644 --- a/tests/ui/suspicious_splitn.stderr +++ b/tests/ui/suspicious_splitn.stderr @@ -55,5 +55,21 @@ 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 +error: `splitn` called with `1` split + --> $DIR/suspicious_splitn.rs:18:13 + | +LL | let _ = "a,b".splitn(X + 1, ','); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the resulting iterator will always return the entire string followed by `None` + +error: `splitn` called with `0` splits + --> $DIR/suspicious_splitn.rs:19:13 + | +LL | let _ = "a,b".splitn(X, ','); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: the resulting iterator will always return `None` + +error: aborting due to 9 previous errors