about summary refs log tree commit diff
path: root/clippy_lints/src/methods
diff options
context:
space:
mode:
authorflip1995 <philipp.krones@embecosm.com>2022-05-05 15:12:52 +0100
committerflip1995 <philipp.krones@embecosm.com>2022-05-05 15:12:52 +0100
commit7cd86aa1be18d608d828239c11e887a762efc92a (patch)
treef2f098e889406b343beba0dbbf58e492c4e5dff6 /clippy_lints/src/methods
parent82f469f81b6daafb448e36c0e811cf2d40836edb (diff)
Merge commit '7c21f91b15b7604f818565646b686d90f99d1baf' into clippyup
Diffstat (limited to 'clippy_lints/src/methods')
-rw-r--r--clippy_lints/src/methods/is_digit_ascii_radix.rs50
-rw-r--r--clippy_lints/src/methods/iter_with_drain.rs91
-rw-r--r--clippy_lints/src/methods/mod.rs109
-rw-r--r--clippy_lints/src/methods/needless_option_take.rs41
-rw-r--r--clippy_lints/src/methods/str_splitn.rs413
-rw-r--r--clippy_lints/src/methods/unnecessary_to_owned.rs23
-rw-r--r--clippy_lints/src/methods/wrong_self_convention.rs2
7 files changed, 471 insertions, 258 deletions
diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints/src/methods/is_digit_ascii_radix.rs
new file mode 100644
index 00000000000..ad333df2f2d
--- /dev/null
+++ b/clippy_lints/src/methods/is_digit_ascii_radix.rs
@@ -0,0 +1,50 @@
+//! Lint for `c.is_digit(10)`
+
+use super::IS_DIGIT_ASCII_RADIX;
+use clippy_utils::{
+    consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs,
+    source::snippet_with_applicability,
+};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'_>,
+    self_arg: &'tcx Expr<'_>,
+    radix: &'tcx Expr<'_>,
+    msrv: Option<&RustcVersion>,
+) {
+    if !meets_msrv(msrv, &msrvs::IS_ASCII_DIGIT) {
+        return;
+    }
+
+    if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() {
+        return;
+    }
+
+    if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) {
+        let (num, replacement) = match radix_val {
+            FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
+            FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),
+            _ => return,
+        };
+        let mut applicability = Applicability::MachineApplicable;
+
+        span_lint_and_sugg(
+            cx,
+            IS_DIGIT_ASCII_RADIX,
+            expr.span,
+            &format!("use of `char::is_digit` with literal radix of {}", num),
+            "try",
+            format!(
+                "{}.{}()",
+                snippet_with_applicability(cx, self_arg.span, "..", &mut applicability),
+                replacement
+            ),
+            applicability,
+        );
+    }
+}
diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs
index 958c3773087..152072e09c7 100644
--- a/clippy_lints/src/methods/iter_with_drain.rs
+++ b/clippy_lints/src/methods/iter_with_drain.rs
@@ -1,72 +1,47 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::Range;
 use clippy_utils::is_integer_const;
-use clippy_utils::ty::is_type_diagnostic_item;
-use clippy_utils::{
-    higher::{self, Range},
-    SpanlessEq,
-};
 use rustc_ast::ast::RangeLimits;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, QPath};
 use rustc_lint::LateContext;
-use rustc_span::symbol::{sym, Symbol};
+use rustc_span::symbol::sym;
 use rustc_span::Span;
 
 use super::ITER_WITH_DRAIN;
 
-const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
-
 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
-    let ty = cx.typeck_results().expr_ty(recv).peel_refs();
-    if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
-        // Refuse to emit `into_iter` suggestion on draining struct fields due
-        // to the strong possibility of processing unmovable field.
-        if let ExprKind::Field(..) = recv.kind {
-            return;
-        }
+    if !matches!(recv.kind, ExprKind::Field(..))
+        && let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
+        && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
+        && matches!(ty_name, sym::Vec | sym::VecDeque)
+        && let Some(range) = Range::hir(arg)
+        && is_full_range(cx, recv, range)
+    {
+        span_lint_and_sugg(
+            cx,
+            ITER_WITH_DRAIN,
+            span.with_hi(expr.span.hi()),
+            &format!("`drain(..)` used on a `{}`", ty_name),
+            "try this",
+            "into_iter()".to_string(),
+            Applicability::MaybeIncorrect,
+        );
+    };
+}
 
-        if let Some(range) = higher::Range::hir(arg) {
-            let left_full = match range {
-                Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
-                Range { start: None, .. } => true,
-                _ => false,
-            };
-            let full = left_full
-                && match range {
-                    Range {
-                        end: Some(end),
-                        limits: RangeLimits::HalfOpen,
-                        ..
-                    } => {
-                        // `x.drain(..x.len())` call
-                        if_chain! {
-                            if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
-                            if len_path.ident.name == sym::len && len_args.len() == 1;
-                            if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
-                            if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
-                            if SpanlessEq::new(cx).eq_path(drain_path, len_path);
-                            then { true }
-                            else { false }
-                        }
-                    },
-                    Range {
-                        end: None,
-                        limits: RangeLimits::HalfOpen,
-                        ..
-                    } => true,
-                    _ => false,
-                };
-            if full {
-                span_lint_and_sugg(
-                    cx,
-                    ITER_WITH_DRAIN,
-                    span.with_hi(expr.span.hi()),
-                    &format!("`drain(..)` used on a `{}`", drained_type),
-                    "try this",
-                    "into_iter()".to_string(),
-                    Applicability::MaybeIncorrect,
-                );
+fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
+    range.start.map_or(true, |e| is_integer_const(cx, e, 0))
+        && range.end.map_or(true, |e| {
+            if range.limits == RangeLimits::HalfOpen
+                && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
+                && let ExprKind::MethodCall(name, [self_arg], _) = e.kind
+                && name.ident.name == sym::len
+                && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
+            {
+                container_path.res == path.res
+            } else {
+                false
             }
-        }
-    }
+        })
 }
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 70d021a1668..f3be71f6b8b 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -26,6 +26,7 @@ mod implicit_clone;
 mod inefficient_to_string;
 mod inspect_for_each;
 mod into_iter_on_ref;
+mod is_digit_ascii_radix;
 mod iter_cloned_collect;
 mod iter_count;
 mod iter_next_slice;
@@ -42,6 +43,7 @@ mod map_flatten;
 mod map_identity;
 mod map_unwrap_or;
 mod needless_option_as_deref;
+mod needless_option_take;
 mod ok_expect;
 mod option_as_ref_deref;
 mod option_map_or_none;
@@ -294,15 +296,15 @@ declare_clippy_lint! {
     /// Checks for methods with certain name prefixes and which
     /// doesn't match how self is taken. The actual rules are:
     ///
-    /// |Prefix |Postfix     |`self` taken           | `self` type  |
-    /// |-------|------------|-----------------------|--------------|
-    /// |`as_`  | none       |`&self` or `&mut self` | any          |
-    /// |`from_`| none       | none                  | any          |
-    /// |`into_`| none       |`self`                 | any          |
-    /// |`is_`  | none       |`&self` or none        | any          |
-    /// |`to_`  | `_mut`     |`&mut self`            | any          |
-    /// |`to_`  | not `_mut` |`self`                 | `Copy`       |
-    /// |`to_`  | not `_mut` |`&self`                | not `Copy`   |
+    /// |Prefix |Postfix     |`self` taken                   | `self` type  |
+    /// |-------|------------|-------------------------------|--------------|
+    /// |`as_`  | none       |`&self` or `&mut self`         | any          |
+    /// |`from_`| none       | none                          | any          |
+    /// |`into_`| none       |`self`                         | any          |
+    /// |`is_`  | none       |`&mut self` or `&self` or none | any          |
+    /// |`to_`  | `_mut`     |`&mut self`                    | any          |
+    /// |`to_`  | not `_mut` |`self`                         | `Copy`       |
+    /// |`to_`  | not `_mut` |`&self`                        | not `Copy`   |
     ///
     /// Note: Clippy doesn't trigger methods with `to_` prefix in:
     /// - Traits definition.
@@ -1265,7 +1267,7 @@ declare_clippy_lint! {
     #[clippy::version = "1.55.0"]
     pub EXTEND_WITH_DRAIN,
     perf,
-    "using vec.append(&mut vec) to move the full range of a vecor to another"
+    "using vec.append(&mut vec) to move the full range of a vector to another"
 }
 
 declare_clippy_lint! {
@@ -2007,13 +2009,27 @@ declare_clippy_lint! {
     /// ### Example
     /// ```rust,ignore
     /// // Bad
-    ///  let (key, value) = _.splitn(2, '=').next_tuple()?;
-    ///  let value = _.splitn(2, '=').nth(1)?;
+    /// let s = "key=value=add";
+    /// let (key, value) = s.splitn(2, '=').next_tuple()?;
+    /// let value = s.splitn(2, '=').nth(1)?;
     ///
+    /// let mut parts = s.splitn(2, '=');
+    /// let key = parts.next()?;
+    /// let value = parts.next()?;
+    /// ```
+    /// Use instead:
+    /// ```rust,ignore
     /// // Good
-    /// let (key, value) = _.split_once('=')?;
-    /// let value = _.split_once('=')?.1;
+    /// let s = "key=value=add";
+    /// let (key, value) = s.split_once('=')?;
+    /// let value = s.split_once('=')?.1;
+    ///
+    /// let (key, value) = s.split_once('=')?;
     /// ```
+    ///
+    /// ### Limitations
+    /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()`
+    /// in two separate `let` statements that immediately follow the `splitn()`
     #[clippy::version = "1.57.0"]
     pub MANUAL_SPLIT_ONCE,
     complexity,
@@ -2099,7 +2115,7 @@ declare_clippy_lint! {
     /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
     /// will prevent loop unrolling and will result in a negative performance impact.
     ///
-    /// Additionlly, differences have been observed between aarch64 and x86_64 assembly output,
+    /// Additionally, differences have been observed between aarch64 and x86_64 assembly output,
     /// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()`
     #[clippy::version = "1.61.0"]
     pub UNNECESSARY_JOIN,
@@ -2131,6 +2147,56 @@ declare_clippy_lint! {
     "no-op use of `deref` or `deref_mut` method to `Option`."
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Finds usages of [`char::is_digit`]
+    /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
+    /// can be replaced with [`is_ascii_digit`]
+    /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
+    /// [`is_ascii_hexdigit`]
+    /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
+    ///
+    /// ### Why is this bad?
+    /// `is_digit(..)` is slower and requires specifying the radix.
+    ///
+    /// ### Example
+    /// ```rust
+    /// let c: char = '6';
+    /// c.is_digit(10);
+    /// c.is_digit(16);
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// let c: char = '6';
+    /// c.is_ascii_digit();
+    /// c.is_ascii_hexdigit();
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub IS_DIGIT_ASCII_RADIX,
+    style,
+    "use of `char::is_digit(..)` with literal radix of 10 or 16"
+}
+
+declare_clippy_lint! {
+    ///
+    /// ### Why is this bad?
+    ///
+    /// ### Example
+    /// ```rust
+    /// let x = Some(3);
+    /// x.as_ref().take();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// let x = Some(3);
+    /// x.as_ref();
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub NEEDLESS_OPTION_TAKE,
+    complexity,
+    "using `.as_ref().take()` on a temporary value"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Option<RustcVersion>,
@@ -2219,6 +2285,8 @@ impl_lint_pass!(Methods => [
     UNNECESSARY_JOIN,
     ERR_EXPECT,
     NEEDLESS_OPTION_AS_DEREF,
+    IS_DIGIT_ASCII_RADIX,
+    NEEDLESS_OPTION_TAKE,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -2254,7 +2322,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
                 single_char_add_str::check(cx, expr, args);
                 into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, args);
                 single_char_pattern::check(cx, expr, method_call.ident.name, args);
-                unnecessary_to_owned::check(cx, expr, method_call.ident.name, args);
+                unnecessary_to_owned::check(cx, expr, method_call.ident.name, args, self.msrv.as_ref());
             },
             hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
                 let mut info = BinaryExprInfo {
@@ -2516,6 +2584,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
             },
             ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
             ("is_file", []) => filetype_is_file::check(cx, expr, recv),
+            ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, msrv),
             ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
             ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
             ("join", [join_arg]) => {
@@ -2574,12 +2643,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
             ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
                 if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
                     suspicious_splitn::check(cx, name, expr, recv, count);
-                    if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
-                        str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg);
-                    }
-                    if count >= 2 {
-                        str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count);
-                    }
+                    str_splitn::check(cx, name, expr, recv, pat_arg, count, msrv);
                 }
             },
             ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
@@ -2595,6 +2659,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                     }
                 }
             },
+            ("take", []) => needless_option_take::check(cx, expr, recv),
             ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
                 implicit_clone::check(cx, name, expr, recv);
             },
diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints/src/methods/needless_option_take.rs
new file mode 100644
index 00000000000..829c118d291
--- /dev/null
+++ b/clippy_lints/src/methods/needless_option_take.rs
@@ -0,0 +1,41 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::NEEDLESS_OPTION_TAKE;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
+    // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place
+    if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) && has_expr_as_ref_path(cx, recv) {
+        let mut applicability = Applicability::MachineApplicable;
+        span_lint_and_sugg(
+            cx,
+            NEEDLESS_OPTION_TAKE,
+            expr.span,
+            "called `Option::take()` on a temporary value",
+            "try",
+            format!(
+                "{}",
+                snippet_with_applicability(cx, recv.span, "..", &mut applicability)
+            ),
+            applicability,
+        );
+    }
+}
+
+fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    let expr_type = cx.typeck_results().expr_ty(expr);
+    is_type_diagnostic_item(cx, expr_type, sym::Option)
+}
+
+fn has_expr_as_ref_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    if let Some(ref_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+        return match_def_path(cx, ref_id, &["core", "option", "Option", "as_ref"]);
+    }
+    false
+}
diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs
index 8125930b304..52891eeed06 100644
--- a/clippy_lints/src/methods/str_splitn.rs
+++ b/clippy_lints/src/methods/str_splitn.rs
@@ -1,36 +1,83 @@
 use clippy_utils::consts::{constant, Constant};
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::source::snippet_with_context;
-use clippy_utils::{is_diag_item_method, match_def_path, paths};
+use clippy_utils::usage::local_used_after_expr;
+use clippy_utils::visitors::expr_visitor;
+use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
-use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::{
+    BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
+};
 use rustc_lint::LateContext;
-use rustc_middle::ty::{self, adjustment::Adjust};
-use rustc_span::{symbol::sym, Span, SyntaxContext};
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span, Symbol, SyntaxContext};
 
-use super::MANUAL_SPLIT_ONCE;
+use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
 
-pub(super) fn check_manual_split_once(
+pub(super) fn check(
     cx: &LateContext<'_>,
     method_name: &str,
     expr: &Expr<'_>,
     self_arg: &Expr<'_>,
     pat_arg: &Expr<'_>,
+    count: u128,
+    msrv: Option<&RustcVersion>,
 ) {
-    if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
+    if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
         return;
     }
 
+    let needless = |usage_kind| match usage_kind {
+        IterUsageKind::Nth(n) => count > n + 1,
+        IterUsageKind::NextTuple => count > 2,
+    };
+    let manual = count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE);
+
+    match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) {
+        Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),
+        Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage),
+        None if manual => {
+            check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg);
+        },
+        _ => {},
+    }
+}
+
+fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
+    let mut app = Applicability::MachineApplicable;
+    let r = if method_name == "splitn" { "" } else { "r" };
+
+    span_lint_and_sugg(
+        cx,
+        NEEDLESS_SPLITN,
+        expr.span,
+        &format!("unnecessary use of `{r}splitn`"),
+        "try this",
+        format!(
+            "{}.{r}split({})",
+            snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0,
+            snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0,
+        ),
+        app,
+    );
+}
+
+fn check_manual_split_once(
+    cx: &LateContext<'_>,
+    method_name: &str,
+    expr: &Expr<'_>,
+    self_arg: &Expr<'_>,
+    pat_arg: &Expr<'_>,
+    usage: &IterUsage,
+) {
     let ctxt = expr.span.ctxt();
-    let (method_name, msg, reverse) = if method_name == "splitn" {
-        ("split_once", "manual implementation of `split_once`", false)
+    let (msg, reverse) = if method_name == "splitn" {
+        ("manual implementation of `split_once`", false)
     } else {
-        ("rsplit_once", "manual implementation of `rsplit_once`", true)
-    };
-    let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) {
-        Some(x) => x,
-        None => return,
+        ("manual implementation of `rsplit_once`", true)
     };
 
     let mut app = Applicability::MachineApplicable;
@@ -39,84 +86,198 @@ pub(super) fn check_manual_split_once(
 
     let sugg = match usage.kind {
         IterUsageKind::NextTuple => {
-            format!("{}.{}({})", self_snip, method_name, pat_snip)
-        },
-        IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
-        IterUsageKind::Next | IterUsageKind::Second => {
-            let self_deref = {
-                let adjust = cx.typeck_results().expr_adjustments(self_arg);
-                if adjust.len() < 2 {
-                    String::new()
-                } else if cx.typeck_results().expr_ty(self_arg).is_box()
-                    || adjust
-                        .iter()
-                        .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
-                {
-                    format!("&{}", "*".repeat(adjust.len().saturating_sub(1)))
-                } else {
-                    "*".repeat(adjust.len().saturating_sub(2))
-                }
-            };
-            if matches!(usage.kind, IterUsageKind::Next) {
-                match usage.unwrap_kind {
-                    Some(UnwrapKind::Unwrap) => {
-                        if reverse {
-                            format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
-                        } else {
-                            format!(
-                                "{}.{}({}).map_or({}{}, |x| x.0)",
-                                self_snip, method_name, pat_snip, self_deref, &self_snip
-                            )
-                        }
-                    },
-                    Some(UnwrapKind::QuestionMark) => {
-                        format!(
-                            "{}.{}({}).map_or({}{}, |x| x.0)",
-                            self_snip, method_name, pat_snip, self_deref, &self_snip
-                        )
-                    },
-                    None => {
-                        format!(
-                            "Some({}.{}({}).map_or({}{}, |x| x.0))",
-                            &self_snip, method_name, pat_snip, self_deref, &self_snip
-                        )
-                    },
-                }
+            if reverse {
+                format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
             } else {
-                match usage.unwrap_kind {
-                    Some(UnwrapKind::Unwrap) => {
-                        if reverse {
-                            // In this case, no better suggestion is offered.
-                            return;
-                        }
-                        format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
-                    },
-                    Some(UnwrapKind::QuestionMark) => {
-                        format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
-                    },
-                    None => {
-                        format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
-                    },
-                }
+                format!("{self_snip}.split_once({pat_snip})")
+            }
+        },
+        IterUsageKind::Nth(1) => {
+            let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
+
+            match usage.unwrap_kind {
+                Some(UnwrapKind::Unwrap) => {
+                    format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
+                },
+                Some(UnwrapKind::QuestionMark) => {
+                    format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
+                },
+                None => {
+                    format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
+                },
             }
         },
+        IterUsageKind::Nth(_) => return,
     };
 
     span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
 }
 
+/// checks for
+///
+/// ```
+/// let mut iter = "a.b.c".splitn(2, '.');
+/// let a = iter.next();
+/// let b = iter.next();
+/// ```
+fn check_manual_split_once_indirect(
+    cx: &LateContext<'_>,
+    method_name: &str,
+    expr: &Expr<'_>,
+    self_arg: &Expr<'_>,
+    pat_arg: &Expr<'_>,
+) -> Option<()> {
+    let ctxt = expr.span.ctxt();
+    let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
+    if let (_, Node::Local(local)) = parents.next()?
+        && let PatKind::Binding(BindingAnnotation::Mutable, iter_binding_id, iter_ident, None) = local.pat.kind
+        && let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
+        && let (_, Node::Block(enclosing_block)) = parents.next()?
+
+        && let mut stmts = enclosing_block
+            .stmts
+            .iter()
+            .skip_while(|stmt| stmt.hir_id != iter_stmt_id)
+            .skip(1)
+
+        && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
+        && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
+        && first.unwrap_kind == second.unwrap_kind
+        && first.name != second.name
+        && !local_used_after_expr(cx, iter_binding_id, second.init_expr)
+    {
+        let (r, lhs, rhs) = if method_name == "splitn" {
+            ("", first.name, second.name)
+        } else {
+            ("r", second.name, first.name)
+        };
+        let msg = format!("manual implementation of `{r}split_once`");
+
+        let mut app = Applicability::MachineApplicable;
+        let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+        let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
+
+        span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| {
+            diag.span_label(first.span, "first usage here");
+            diag.span_label(second.span, "second usage here");
+
+            let unwrap = match first.unwrap_kind {
+                UnwrapKind::Unwrap => ".unwrap()",
+                UnwrapKind::QuestionMark => "?",
+            };
+            diag.span_suggestion_verbose(
+                local.span,
+                &format!("try `{r}split_once`"),
+                format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
+                app,
+            );
+
+            let remove_msg = format!("remove the `{iter_ident}` usages");
+            diag.span_suggestion(
+                first.span,
+                &remove_msg,
+                String::new(),
+                app,
+            );
+            diag.span_suggestion(
+                second.span,
+                &remove_msg,
+                String::new(),
+                app,
+            );
+        });
+    }
+
+    Some(())
+}
+
+#[derive(Debug)]
+struct IndirectUsage<'a> {
+    name: Symbol,
+    span: Span,
+    init_expr: &'a Expr<'a>,
+    unwrap_kind: UnwrapKind,
+}
+
+/// returns `Some(IndirectUsage)` for e.g.
+///
+/// ```ignore
+/// let name = binding.next()?;
+/// let name = binding.next().unwrap();
+/// ```
+fn indirect_usage<'tcx>(
+    cx: &LateContext<'tcx>,
+    stmt: &Stmt<'tcx>,
+    binding: HirId,
+    ctxt: SyntaxContext,
+) -> Option<IndirectUsage<'tcx>> {
+    if let StmtKind::Local(Local {
+        pat:
+            Pat {
+                kind: PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None),
+                ..
+            },
+        init: Some(init_expr),
+        hir_id: local_hir_id,
+        ..
+    }) = stmt.kind
+    {
+        let mut path_to_binding = None;
+        expr_visitor(cx, |expr| {
+            if path_to_local_id(expr, binding) {
+                path_to_binding = Some(expr);
+            }
+
+            path_to_binding.is_none()
+        })
+        .visit_expr(init_expr);
+
+        let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
+        let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
+
+        let (parent_id, _) = parents.find(|(_, node)| {
+            !matches!(
+                node,
+                Node::Expr(Expr {
+                    kind: ExprKind::Match(.., MatchSource::TryDesugar),
+                    ..
+                })
+            )
+        })?;
+
+        if let IterUsage {
+            kind: IterUsageKind::Nth(0),
+            unwrap_kind: Some(unwrap_kind),
+            ..
+        } = iter_usage
+        {
+            if parent_id == *local_hir_id {
+                return Some(IndirectUsage {
+                    name: ident.name,
+                    span: stmt.span,
+                    init_expr,
+                    unwrap_kind,
+                });
+            }
+        }
+    }
+
+    None
+}
+
+#[derive(Debug, Clone, Copy)]
 enum IterUsageKind {
-    Next,
-    Second,
+    Nth(u128),
     NextTuple,
-    RNextTuple,
 }
 
+#[derive(Debug, PartialEq)]
 enum UnwrapKind {
     Unwrap,
     QuestionMark,
 }
 
+#[derive(Debug)]
 struct IterUsage {
     kind: IterUsageKind,
     unwrap_kind: Option<UnwrapKind>,
@@ -128,7 +289,6 @@ fn parse_iter_usage<'tcx>(
     cx: &LateContext<'tcx>,
     ctxt: SyntaxContext,
     mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
-    reverse: bool,
 ) -> Option<IterUsage> {
     let (kind, span) = match iter.next() {
         Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
@@ -141,13 +301,7 @@ fn parse_iter_usage<'tcx>(
             let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
 
             match (name.ident.as_str(), args) {
-                ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
-                    if reverse {
-                        (IterUsageKind::Second, e.span)
-                    } else {
-                        (IterUsageKind::Next, e.span)
-                    }
-                },
+                ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
                 ("next_tuple", []) => {
                     return if_chain! {
                         if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
@@ -157,7 +311,7 @@ fn parse_iter_usage<'tcx>(
                         if subs.len() == 2;
                         then {
                             Some(IterUsage {
-                                kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple },
+                                kind: IterUsageKind::NextTuple,
                                 span: e.span,
                                 unwrap_kind: None
                             })
@@ -185,11 +339,7 @@ fn parse_iter_usage<'tcx>(
                                 }
                             }
                         };
-                        match if reverse { idx ^ 1 } else { idx } {
-                            0 => (IterUsageKind::Next, span),
-                            1 => (IterUsageKind::Second, span),
-                            _ => return None,
-                        }
+                        (IterUsageKind::Nth(idx), span)
                     } else {
                         return None;
                     }
@@ -238,86 +388,3 @@ fn parse_iter_usage<'tcx>(
         span,
     })
 }
-
-use super::NEEDLESS_SPLITN;
-
-pub(super) fn check_needless_splitn(
-    cx: &LateContext<'_>,
-    method_name: &str,
-    expr: &Expr<'_>,
-    self_arg: &Expr<'_>,
-    pat_arg: &Expr<'_>,
-    count: u128,
-) {
-    if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
-        return;
-    }
-    let ctxt = expr.span.ctxt();
-    let mut app = Applicability::MachineApplicable;
-    let (reverse, message) = if method_name == "splitn" {
-        (false, "unnecessary use of `splitn`")
-    } else {
-        (true, "unnecessary use of `rsplitn`")
-    };
-    if_chain! {
-        if count >= 2;
-        if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
-        then {
-            span_lint_and_sugg(
-                cx,
-                NEEDLESS_SPLITN,
-                expr.span,
-                message,
-                "try this",
-                format!(
-                    "{}.{}({})",
-                    snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0,
-                    if reverse {"rsplit"} else {"split"},
-                    snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0
-                ),
-                app,
-            );
-        }
-    }
-}
-
-fn check_iter<'tcx>(
-    cx: &LateContext<'tcx>,
-    ctxt: SyntaxContext,
-    mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
-    count: u128,
-) -> bool {
-    match iter.next() {
-        Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
-            let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
-                (name, args)
-            } else {
-                return false;
-            };
-            if_chain! {
-                if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id);
-                if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
-                then {
-                    match (name.ident.as_str(), args) {
-                        ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
-                            return true;
-                        },
-                        ("next_tuple", []) if count > 2 => {
-                            return true;
-                        },
-                        ("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
-                            if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
-                                if count > idx + 1 {
-                                    return true;
-                                }
-                            }
-                        },
-                        _ =>  return false,
-                    }
-                }
-            }
-        },
-        _ => return false,
-    };
-    false
-}
diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs
index 1555758fc4a..02b882e8b55 100644
--- a/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -5,6 +5,8 @@ use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::{
     contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
 };
+use clippy_utils::{meets_msrv, msrvs};
+
 use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
 use rustc_errors::Applicability;
 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
@@ -13,12 +15,19 @@ use rustc_middle::mir::Mutability;
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
 use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
+use rustc_semver::RustcVersion;
 use rustc_span::{sym, Symbol};
 use std::cmp::max;
 
 use super::UNNECESSARY_TO_OWNED;
 
-pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
+pub fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'tcx>,
+    method_name: Symbol,
+    args: &'tcx [Expr<'tcx>],
+    msrv: Option<&RustcVersion>,
+) {
     if_chain! {
         if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
         if let [receiver] = args;
@@ -33,7 +42,7 @@ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name:
                 if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
                     return;
                 }
-                if check_into_iter_call_arg(cx, expr, method_name, receiver) {
+                if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
                     return;
                 }
                 check_other_call_arg(cx, expr, method_name, receiver);
@@ -178,7 +187,13 @@ fn check_addr_of_expr(
 
 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
 /// call of a `to_owned`-like function is unnecessary.
-fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
+fn check_into_iter_call_arg(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    method_name: Symbol,
+    receiver: &Expr<'_>,
+    msrv: Option<&RustcVersion>,
+) -> bool {
     if_chain! {
         if let Some(parent) = get_parent_expr(cx, expr);
         if let Some(callee_def_id) = fn_def_id(cx, parent);
@@ -192,7 +207,7 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name:
             if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
                 return true;
             }
-            let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" };
+            let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, &msrvs::ITERATOR_COPIED) { "copied" } else { "cloned" };
             // The next suggestion may be incorrect because the removal of the `to_owned`-like
             // function could cause the iterator to hold a reference to a resource that is used
             // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs
index aecfea9c141..4b368d3ffae 100644
--- a/clippy_lints/src/methods/wrong_self_convention.rs
+++ b/clippy_lints/src/methods/wrong_self_convention.rs
@@ -14,7 +14,7 @@ const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
     (&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
     (&[Convention::StartsWith("from_")], &[SelfKind::No]),
     (&[Convention::StartsWith("into_")], &[SelfKind::Value]),
-    (&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]),
+    (&[Convention::StartsWith("is_")], &[SelfKind::RefMut, SelfKind::Ref, SelfKind::No]),
     (&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
     (&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),