diff options
| author | yanglsh <yanglsh@shanghaitech.edu.cn> | 2025-03-28 13:25:46 +0800 |
|---|---|---|
| committer | yanglsh <yanglsh@shanghaitech.edu.cn> | 2025-04-19 16:45:58 +0800 |
| commit | 86a10f01d10c28c26b280b913d57d3e8199ffa86 (patch) | |
| tree | 038ac635751a060d31f47e60e64dae909d566ee8 | |
| parent | 781fdab9a95320bf8c3eb092ef628352643059bf (diff) | |
| download | rust-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.rs | 4 | ||||
| -rw-r--r-- | clippy_utils/src/ty/mod.rs | 31 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last.fixed | 11 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last.rs | 11 |
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(); +} |
