.last() to .next_back() requires a mutable receiver

In the case where `iter` is a `DoubleEndedIterator`, replacing a call to
`iter.last()` (which consumes `iter`) by `iter.next_back()` (which
requires a mutable reference to `iter`) cannot be done when `iter`
Is not a mutable binding or a mutable reference.

When `iter` is a local binding, it can be made mutable by fixing its
definition site.
This commit is contained in:
Samuel Tardieu 2025-02-02 17:16:36 +01:00
parent 510d3b69fc
commit 45f7a60d31
6 changed files with 169 additions and 21 deletions

View file

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_mutable, is_trait_method, path_to_local};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::{Expr, Node, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Instance;
use rustc_span::{Span, sym};
@ -28,14 +28,40 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
// if the resolved method is the same as the provided definition
&& fn_def.def_id() == last_def.def_id
{
span_lint_and_sugg(
let mut sugg = vec![(call_span, String::from("next_back()"))];
let mut dont_apply = false;
// if `self_expr` is a reference, it is mutable because it is used for `.last()`
if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
if let Some(hir_id) = path_to_local(self_expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& let PatKind::Binding(_, _, ident, _) = pat.kind
{
sugg.push((ident.span.shrink_to_lo(), String::from("mut ")));
} else {
// If we can't make the binding mutable, make the suggestion `Unspecified` to prevent it from being
// automatically applied, and add a complementary help message.
dont_apply = true;
}
}
span_lint_and_then(
cx,
DOUBLE_ENDED_ITERATOR_LAST,
call_span,
expr.span,
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator",
"try",
"next_back()".to_string(),
Applicability::MachineApplicable,
|diag| {
diag.multipart_suggestion(
"try",
sugg,
if dont_apply {
Applicability::Unspecified
} else {
Applicability::MachineApplicable
},
);
if dont_apply {
diag.span_note(self_expr.span, "this must be made mutable to use `.next_back()`");
}
},
);
}
}