about summary refs log tree commit diff
diff options
context:
space:
mode:
authoryanglsh <yanglsh@shanghaitech.edu.cn>2025-03-28 13:25:46 +0800
committeryanglsh <yanglsh@shanghaitech.edu.cn>2025-04-19 16:45:58 +0800
commit86a10f01d10c28c26b280b913d57d3e8199ffa86 (patch)
tree038ac635751a060d31f47e60e64dae909d566ee8
parent781fdab9a95320bf8c3eb092ef628352643059bf (diff)
downloadrust-86a10f01d10c28c26b280b913d57d3e8199ffa86.tar.gz
rust-86a10f01d10c28c26b280b913d57d3e8199ffa86.zip
fix: `double_ended_iterator_last` FP when iter has side effects
-rw-r--r--clippy_lints/src/methods/double_ended_iterator_last.rs4
-rw-r--r--clippy_utils/src/ty/mod.rs31
-rw-r--r--tests/ui/double_ended_iterator_last.fixed11
-rw-r--r--tests/ui/double_ended_iterator_last.rs11
4 files changed, 56 insertions, 1 deletions
diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints/src/methods/double_ended_iterator_last.rs
index b5adc69e9a7..88e357ec45d 100644
--- a/clippy_lints/src/methods/double_ended_iterator_last.rs
+++ b/clippy_lints/src/methods/double_ended_iterator_last.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::ty::implements_trait;
+use clippy_utils::ty::{implements_trait, is_iter_with_side_effects};
 use clippy_utils::{is_mutable, is_trait_method, path_to_local};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, Node, PatKind};
@@ -27,6 +27,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
         && let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name().as_str() == "last")
         // if the resolved method is the same as the provided definition
         && fn_def.def_id() == last_def.def_id
+        && let self_ty = cx.typeck_results().expr_ty(self_expr)
+        && !is_iter_with_side_effects(cx, self_ty)
     {
         let mut sugg = vec![(call_span, String::from("next_back()"))];
         let mut dont_apply = false;
diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs
index 14ccd35afdd..5b48ffa4426 100644
--- a/clippy_utils/src/ty/mod.rs
+++ b/clippy_utils/src/ty/mod.rs
@@ -1376,3 +1376,34 @@ pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
         _ => None,
     }
 }
+
+/// Check if `ty` is an `Iterator` and has side effects when iterated over. Currently, this only
+/// checks if the `ty` contains mutable captures, and thus may be imcomplete.
+pub fn is_iter_with_side_effects<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>) -> bool {
+    let Some(iter_trait) = cx.tcx.lang_items().iterator_trait() else {
+        return false;
+    };
+
+    is_iter_with_side_effects_impl(cx, iter_ty, iter_trait)
+}
+
+fn is_iter_with_side_effects_impl<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, iter_trait: DefId) -> bool {
+    if implements_trait(cx, iter_ty, iter_trait, &[])
+        && let ty::Adt(_, args) = iter_ty.kind()
+    {
+        return args.types().any(|arg_ty| {
+            if let ty::Closure(_, closure_args) = arg_ty.kind()
+                && let Some(captures) = closure_args.types().next_back()
+            {
+                captures
+                    .tuple_fields()
+                    .iter()
+                    .any(|capture_ty| matches!(capture_ty.ref_mutability(), Some(Mutability::Mut)))
+            } else {
+                is_iter_with_side_effects_impl(cx, arg_ty, iter_trait)
+            }
+        });
+    }
+
+    false
+}
diff --git a/tests/ui/double_ended_iterator_last.fixed b/tests/ui/double_ended_iterator_last.fixed
index 17d0d71a885..07a3ab4cf88 100644
--- a/tests/ui/double_ended_iterator_last.fixed
+++ b/tests/ui/double_ended_iterator_last.fixed
@@ -90,3 +90,14 @@ fn drop_order() {
     //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
     println!("Done");
 }
+
+fn issue_14444() {
+    let mut squares = vec![];
+    let last_square = [1, 2, 3]
+        .into_iter()
+        .map(|x| {
+            squares.push(x * x);
+            Some(x * x)
+        })
+        .last();
+}
diff --git a/tests/ui/double_ended_iterator_last.rs b/tests/ui/double_ended_iterator_last.rs
index 41bc669b171..385462e5c2b 100644
--- a/tests/ui/double_ended_iterator_last.rs
+++ b/tests/ui/double_ended_iterator_last.rs
@@ -90,3 +90,14 @@ fn drop_order() {
     //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
     println!("Done");
 }
+
+fn issue_14444() {
+    let mut squares = vec![];
+    let last_square = [1, 2, 3]
+        .into_iter()
+        .map(|x| {
+            squares.push(x * x);
+            Some(x * x)
+        })
+        .last();
+}