diff options
| author | yanglsh <yanglsh@shanghaitech.edu.cn> | 2025-04-01 20:14:59 +0800 |
|---|---|---|
| committer | yanglsh <yanglsh@shanghaitech.edu.cn> | 2025-04-22 20:55:07 +0800 |
| commit | a50e043d3226822768922e67c64c5376edbf7dfe (patch) | |
| tree | c390e798f42818f8f01798745f7f9d90942234c7 | |
| parent | c6d76bb69d5ed9f12284007338d3b7d04bcf4385 (diff) | |
| download | rust-a50e043d3226822768922e67c64c5376edbf7dfe.tar.gz rust-a50e043d3226822768922e67c64c5376edbf7dfe.zip | |
Expand mutable capture check for `is_iter_with_side_effects()`
| -rw-r--r-- | clippy_lints/src/methods/double_ended_iterator_last.rs | 7 | ||||
| -rw-r--r-- | clippy_lints/src/methods/needless_collect.rs | 4 | ||||
| -rw-r--r-- | clippy_utils/src/ty/mod.rs | 67 | ||||
| -rw-r--r-- | tests/ui/crashes/ice-11230.fixed | 4 | ||||
| -rw-r--r-- | tests/ui/crashes/ice-11230.rs | 2 | ||||
| -rw-r--r-- | tests/ui/crashes/ice-11230.stderr | 11 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last.fixed | 21 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last.rs | 17 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last.stderr | 52 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last_unfixable.rs | 5 | ||||
| -rw-r--r-- | tests/ui/double_ended_iterator_last_unfixable.stderr | 24 | ||||
| -rw-r--r-- | tests/ui/needless_collect.fixed | 57 | ||||
| -rw-r--r-- | tests/ui/needless_collect.rs | 57 |
13 files changed, 201 insertions, 127 deletions
diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints/src/methods/double_ended_iterator_last.rs index 88e357ec45d..e666f31217c 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, is_iter_with_side_effects}; +use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait}; use clippy_utils::{is_mutable, is_trait_method, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{Expr, Node, PatKind}; @@ -28,11 +28,14 @@ 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 && let self_ty = cx.typeck_results().expr_ty(self_expr) - && !is_iter_with_side_effects(cx, self_ty) + && !has_non_owning_mutable_access(cx, self_ty) { 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()` + // TODO: Change this to lint only when the referred iterator is not used later. If it is used later, + // changing to `next_back()` may change its behavior. 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) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index eec96e383e7..6efaba525e3 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{ - get_type_diagnostic_name, is_iter_with_side_effects, make_normalized_projection, make_projection, + get_type_diagnostic_name, has_non_owning_mutable_access, make_normalized_projection, make_projection, }; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, is_trait_method, path_to_local, @@ -34,7 +34,7 @@ pub(super) fn check<'tcx>( call_span: Span, ) { let iter_ty = cx.typeck_results().expr_ty(iter_expr); - if is_iter_with_side_effects(cx, iter_ty) { + if has_non_owning_mutable_access(cx, iter_ty) { return; // don't lint if the iterator has side effects } diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 5b48ffa4426..8db9cd593b3 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -1377,33 +1377,48 @@ pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t } } -/// 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) -} +/// Check if a Ty<'_> of `Iterator` contains any mutable access to non-owning types by checking if +/// it contains fields of mutable references or pointers, or references/pointers to non-`Freeze` +/// types, or `PhantomData` types containing any of the previous. This can be used to check whether +/// skipping iterating over an iterator will change its behavior. +pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>) -> bool { + fn normalize_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty) + } -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) - } - }); + /// Check if `ty` contains mutable references or equivalent, which includes: + /// - A mutable reference/pointer. + /// - A reference/pointer to a non-`Freeze` type. + /// - A `PhantomData` type containing any of the previous. + fn has_non_owning_mutable_access_inner<'tcx>( + cx: &LateContext<'tcx>, + phantoms: &mut FxHashSet<Ty<'tcx>>, + ty: Ty<'tcx>, + ) -> bool { + match ty.kind() { + ty::Adt(adt_def, args) if adt_def.is_phantom_data() => { + phantoms.insert(ty) + && args + .types() + .any(|arg_ty| has_non_owning_mutable_access_inner(cx, phantoms, arg_ty)) + }, + ty::Adt(adt_def, args) => adt_def.all_fields().any(|field| { + has_non_owning_mutable_access_inner(cx, phantoms, normalize_ty(cx, field.ty(cx.tcx, args))) + }), + ty::Array(elem_ty, _) | ty::Slice(elem_ty) => has_non_owning_mutable_access_inner(cx, phantoms, *elem_ty), + ty::RawPtr(pointee_ty, mutability) | ty::Ref(_, pointee_ty, mutability) => { + mutability.is_mut() || !pointee_ty.is_freeze(cx.tcx, cx.typing_env()) + }, + ty::Closure(_, closure_args) => { + matches!(closure_args.types().next_back(), Some(captures) if has_non_owning_mutable_access_inner(cx, phantoms, captures)) + }, + ty::Tuple(tuple_args) => tuple_args + .iter() + .any(|arg_ty| has_non_owning_mutable_access_inner(cx, phantoms, arg_ty)), + _ => false, + } } - false + let mut phantoms = FxHashSet::default(); + has_non_owning_mutable_access_inner(cx, &mut phantoms, iter_ty) } diff --git a/tests/ui/crashes/ice-11230.fixed b/tests/ui/crashes/ice-11230.fixed index 181e1ebbe5a..c49a419f0d4 100644 --- a/tests/ui/crashes/ice-11230.fixed +++ b/tests/ui/crashes/ice-11230.fixed @@ -12,7 +12,7 @@ fn main() { // needless_collect trait Helper<'a>: Iterator<Item = fn()> {} +// Should not be linted because we have no idea whether the iterator has side effects fn x(w: &mut dyn for<'a> Helper<'a>) { - w.next().is_none(); - //~^ needless_collect + w.collect::<Vec<_>>().is_empty(); } diff --git a/tests/ui/crashes/ice-11230.rs b/tests/ui/crashes/ice-11230.rs index fb05dc781bc..f66b7e961c8 100644 --- a/tests/ui/crashes/ice-11230.rs +++ b/tests/ui/crashes/ice-11230.rs @@ -12,7 +12,7 @@ fn main() { // needless_collect trait Helper<'a>: Iterator<Item = fn()> {} +// Should not be linted because we have no idea whether the iterator has side effects fn x(w: &mut dyn for<'a> Helper<'a>) { w.collect::<Vec<_>>().is_empty(); - //~^ needless_collect } diff --git a/tests/ui/crashes/ice-11230.stderr b/tests/ui/crashes/ice-11230.stderr index b4a3f67081a..91d59121ac4 100644 --- a/tests/ui/crashes/ice-11230.stderr +++ b/tests/ui/crashes/ice-11230.stderr @@ -7,14 +7,5 @@ LL | for v in A.iter() {} = note: `-D clippy::explicit-iter-loop` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::explicit_iter_loop)]` -error: avoid using `collect()` when not needed - --> tests/ui/crashes/ice-11230.rs:16:7 - | -LL | w.collect::<Vec<_>>().is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` - | - = note: `-D clippy::needless-collect` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::needless_collect)]` - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error diff --git a/tests/ui/double_ended_iterator_last.fixed b/tests/ui/double_ended_iterator_last.fixed index 07a3ab4cf88..2ce0c04c301 100644 --- a/tests/ui/double_ended_iterator_last.fixed +++ b/tests/ui/double_ended_iterator_last.fixed @@ -52,28 +52,35 @@ fn main() { let _ = CustomLast.last(); } +// Should not be linted because applying the lint would move the original iterator. This can only be +// linted if the iterator is used thereafter. fn issue_14139() { let mut index = [true, true, false, false, false, true].iter(); - let mut subindex = index.by_ref().take(3); - let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let subindex = index.by_ref().take(3); + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let mut subindex = index.by_ref().take(3); - let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let mut subindex = index.by_ref().take(3); let subindex = &mut subindex; - let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let mut subindex = index.by_ref().take(3); let subindex = &mut subindex; - let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); - let (mut subindex, _) = (index.by_ref().take(3), 42); - let _ = subindex.next_back(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let (subindex, _) = (index.by_ref().take(3), 42); + let _ = subindex.last(); + let _ = index.next(); } fn drop_order() { diff --git a/tests/ui/double_ended_iterator_last.rs b/tests/ui/double_ended_iterator_last.rs index 385462e5c2b..a4eb9b3337b 100644 --- a/tests/ui/double_ended_iterator_last.rs +++ b/tests/ui/double_ended_iterator_last.rs @@ -52,28 +52,35 @@ fn main() { let _ = CustomLast.last(); } +// Should not be linted because applying the lint would move the original iterator. This can only be +// linted if the iterator is used thereafter. fn issue_14139() { let mut index = [true, true, false, false, false, true].iter(); let subindex = index.by_ref().take(3); - let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let mut subindex = index.by_ref().take(3); - let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let mut subindex = index.by_ref().take(3); let subindex = &mut subindex; - let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let mut subindex = index.by_ref().take(3); let subindex = &mut subindex; - let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); let mut index = [true, true, false, false, false, true].iter(); let (subindex, _) = (index.by_ref().take(3), 42); - let _ = subindex.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.last(); + let _ = index.next(); } fn drop_order() { diff --git a/tests/ui/double_ended_iterator_last.stderr b/tests/ui/double_ended_iterator_last.stderr index 1702a24d7a0..fe8cf2dcb25 100644 --- a/tests/ui/double_ended_iterator_last.stderr +++ b/tests/ui/double_ended_iterator_last.stderr @@ -18,55 +18,7 @@ LL | let _ = DeIterator.last(); | help: try: `next_back()` error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:58:13 - | -LL | let _ = subindex.last(); - | ^^^^^^^^^^^^^^^ - | -help: try - | -LL ~ let mut subindex = index.by_ref().take(3); -LL ~ let _ = subindex.next_back(); - | - -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:62:13 - | -LL | let _ = subindex.last(); - | ^^^^^^^^^------ - | | - | help: try: `next_back()` - -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:67:13 - | -LL | let _ = subindex.last(); - | ^^^^^^^^^------ - | | - | help: try: `next_back()` - -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:72:13 - | -LL | let _ = subindex.last(); - | ^^^^^^^^^------ - | | - | help: try: `next_back()` - -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:76:13 - | -LL | let _ = subindex.last(); - | ^^^^^^^^^^^^^^^ - | -help: try - | -LL ~ let (mut subindex, _) = (index.by_ref().take(3), 42); -LL ~ let _ = subindex.next_back(); - | - -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:89:36 + --> tests/ui/double_ended_iterator_last.rs:96:36 | LL | println!("Last element is {}", v.last().unwrap().0); | ^^^^^^^^ @@ -78,5 +30,5 @@ LL ~ let mut v = v.into_iter(); LL ~ println!("Last element is {}", v.next_back().unwrap().0); | -error: aborting due to 8 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/double_ended_iterator_last_unfixable.rs b/tests/ui/double_ended_iterator_last_unfixable.rs index 3f125c7f20c..7c5de8832d6 100644 --- a/tests/ui/double_ended_iterator_last_unfixable.rs +++ b/tests/ui/double_ended_iterator_last_unfixable.rs @@ -1,10 +1,13 @@ //@no-rustfix #![warn(clippy::double_ended_iterator_last)] +// Should not be linted because applying the lint would move the original iterator. This can only be +// linted if the iterator is used thereafter. fn main() { let mut index = [true, true, false, false, false, true].iter(); let subindex = (index.by_ref().take(3), 42); - let _ = subindex.0.last(); //~ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + let _ = subindex.0.last(); + let _ = index.next(); } fn drop_order() { diff --git a/tests/ui/double_ended_iterator_last_unfixable.stderr b/tests/ui/double_ended_iterator_last_unfixable.stderr index f4be757d00d..845afc11f04 100644 --- a/tests/ui/double_ended_iterator_last_unfixable.stderr +++ b/tests/ui/double_ended_iterator_last_unfixable.stderr @@ -1,21 +1,5 @@ error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last_unfixable.rs:7:13 - | -LL | let _ = subindex.0.last(); - | ^^^^^^^^^^^------ - | | - | help: try: `next_back()` - | -note: this must be made mutable to use `.next_back()` - --> tests/ui/double_ended_iterator_last_unfixable.rs:7:13 - | -LL | let _ = subindex.0.last(); - | ^^^^^^^^^^ - = note: `-D clippy::double-ended-iterator-last` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]` - -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last_unfixable.rs:20:36 + --> tests/ui/double_ended_iterator_last_unfixable.rs:23:36 | LL | println!("Last element is {}", v.0.last().unwrap().0); | ^^^^------ @@ -24,10 +8,12 @@ LL | println!("Last element is {}", v.0.last().unwrap().0); | = note: this change will alter drop order which may be undesirable note: this must be made mutable to use `.next_back()` - --> tests/ui/double_ended_iterator_last_unfixable.rs:20:36 + --> tests/ui/double_ended_iterator_last_unfixable.rs:23:36 | LL | println!("Last element is {}", v.0.last().unwrap().0); | ^^^ + = note: `-D clippy::double-ended-iterator-last` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]` -error: aborting due to 2 previous errors +error: aborting due to 1 previous error diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index e41ac72b232..b09efe9888f 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -128,13 +128,45 @@ fn bar<I: IntoIterator<Item = usize>>(_: Vec<usize>, _: I) {} fn baz<I: IntoIterator<Item = usize>>(_: I, _: (), _: impl IntoIterator<Item = char>) {} mod issue9191 { + use std::cell::Cell; use std::collections::HashSet; + use std::hash::Hash; + use std::marker::PhantomData; + use std::ops::Deref; - fn foo(xs: Vec<i32>, mut ys: HashSet<i32>) { + fn captures_ref_mut(xs: Vec<i32>, mut ys: HashSet<i32>) { if xs.iter().map(|x| ys.remove(x)).collect::<Vec<_>>().contains(&true) { todo!() } } + + #[derive(Debug, Clone)] + struct MyRef<'a>(PhantomData<&'a mut Cell<HashSet<i32>>>, *mut Cell<HashSet<i32>>); + + impl MyRef<'_> { + fn new(target: &mut Cell<HashSet<i32>>) -> Self { + MyRef(PhantomData, target) + } + + fn get(&mut self) -> &mut Cell<HashSet<i32>> { + unsafe { &mut *self.1 } + } + } + + fn captures_phantom(xs: Vec<i32>, mut ys: Cell<HashSet<i32>>) { + let mut ys_ref = MyRef::new(&mut ys); + if xs + .iter() + .map({ + let mut ys_ref = ys_ref.clone(); + move |x| ys_ref.get().get_mut().remove(x) + }) + .collect::<Vec<_>>() + .contains(&true) + { + todo!() + } + } } pub fn issue8055(v: impl IntoIterator<Item = i32>) -> Result<impl Iterator<Item = i32>, usize> { @@ -155,3 +187,26 @@ pub fn issue8055(v: impl IntoIterator<Item = i32>) -> Result<impl Iterator<Item } Ok(res.into_iter()) } + +mod issue8055_regression { + struct Foo<T> { + inner: T, + marker: core::marker::PhantomData<Self>, + } + + impl<T: Iterator> Iterator for Foo<T> { + type Item = T::Item; + fn next(&mut self) -> Option<Self::Item> { + self.inner.next() + } + } + + fn foo() { + Foo { + inner: [].iter(), + marker: core::marker::PhantomData, + } + .collect::<Vec<&i32>>() + .len(); + } +} diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 821a6d8017e..da4182966bb 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -128,13 +128,45 @@ fn bar<I: IntoIterator<Item = usize>>(_: Vec<usize>, _: I) {} fn baz<I: IntoIterator<Item = usize>>(_: I, _: (), _: impl IntoIterator<Item = char>) {} mod issue9191 { + use std::cell::Cell; use std::collections::HashSet; + use std::hash::Hash; + use std::marker::PhantomData; + use std::ops::Deref; - fn foo(xs: Vec<i32>, mut ys: HashSet<i32>) { + fn captures_ref_mut(xs: Vec<i32>, mut ys: HashSet<i32>) { if xs.iter().map(|x| ys.remove(x)).collect::<Vec<_>>().contains(&true) { todo!() } } + + #[derive(Debug, Clone)] + struct MyRef<'a>(PhantomData<&'a mut Cell<HashSet<i32>>>, *mut Cell<HashSet<i32>>); + + impl MyRef<'_> { + fn new(target: &mut Cell<HashSet<i32>>) -> Self { + MyRef(PhantomData, target) + } + + fn get(&mut self) -> &mut Cell<HashSet<i32>> { + unsafe { &mut *self.1 } + } + } + + fn captures_phantom(xs: Vec<i32>, mut ys: Cell<HashSet<i32>>) { + let mut ys_ref = MyRef::new(&mut ys); + if xs + .iter() + .map({ + let mut ys_ref = ys_ref.clone(); + move |x| ys_ref.get().get_mut().remove(x) + }) + .collect::<Vec<_>>() + .contains(&true) + { + todo!() + } + } } pub fn issue8055(v: impl IntoIterator<Item = i32>) -> Result<impl Iterator<Item = i32>, usize> { @@ -155,3 +187,26 @@ pub fn issue8055(v: impl IntoIterator<Item = i32>) -> Result<impl Iterator<Item } Ok(res.into_iter()) } + +mod issue8055_regression { + struct Foo<T> { + inner: T, + marker: core::marker::PhantomData<Self>, + } + + impl<T: Iterator> Iterator for Foo<T> { + type Item = T::Item; + fn next(&mut self) -> Option<Self::Item> { + self.inner.next() + } + } + + fn foo() { + Foo { + inner: [].iter(), + marker: core::marker::PhantomData, + } + .collect::<Vec<&i32>>() + .len(); + } +} |
