Skip to content

Commit 889e82a

Browse files
committed
Expand mutable capture check for is_iter_with_side_effects()
1 parent c6d76bb commit 889e82a

File tree

3 files changed

+111
-19
lines changed

3 files changed

+111
-19
lines changed

clippy_utils/src/ty/mod.rs

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,8 +1377,8 @@ pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
13771377
}
13781378
}
13791379

1380-
/// Check if `ty` is an `Iterator` and has side effects when iterated over. Currently, this only
1381-
/// checks if the `ty` contains mutable captures, and thus may be imcomplete.
1380+
/// Check if a Ty<'_> of `Iterator` has side effects when iterated over by checking if it
1381+
/// captures any mutable references or equivalents.
13821382
pub fn is_iter_with_side_effects<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>) -> bool {
13831383
let Some(iter_trait) = cx.tcx.lang_items().iterator_trait() else {
13841384
return false;
@@ -1388,22 +1388,50 @@ pub fn is_iter_with_side_effects<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>
13881388
}
13891389

13901390
fn is_iter_with_side_effects_impl<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, iter_trait: DefId) -> bool {
1391-
if implements_trait(cx, iter_ty, iter_trait, &[])
1392-
&& let ty::Adt(_, args) = iter_ty.kind()
1393-
{
1394-
return args.types().any(|arg_ty| {
1395-
if let ty::Closure(_, closure_args) = arg_ty.kind()
1396-
&& let Some(captures) = closure_args.types().next_back()
1397-
{
1398-
captures
1399-
.tuple_fields()
1400-
.iter()
1401-
.any(|capture_ty| matches!(capture_ty.ref_mutability(), Some(Mutability::Mut)))
1402-
} else {
1403-
is_iter_with_side_effects_impl(cx, arg_ty, iter_trait)
1404-
}
1405-
});
1391+
if let ty::Adt(adt_def, args) = iter_ty.kind() {
1392+
return adt_def
1393+
.all_fields()
1394+
.any(|field| is_ty_with_side_effects(cx, field.ty(cx.tcx, args), iter_trait));
14061395
}
14071396

14081397
false
14091398
}
1399+
1400+
fn is_ty_with_side_effects<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, iter_trait: DefId) -> bool {
1401+
match ty.kind() {
1402+
ty::RawPtr(..) | ty::Ref(..) | ty::Adt(..) => is_mutable_reference_or_equivalent_and(cx, ty, |inner_ty| {
1403+
!implements_trait(cx, inner_ty, iter_trait, &[]) || is_iter_with_side_effects_impl(cx, inner_ty, iter_trait)
1404+
}),
1405+
ty::Closure(_, closure_args) => {
1406+
matches!(closure_args.types().next_back(), Some(captures) if captures.tuple_fields().iter().any(|capture_ty| is_ty_with_side_effects(cx, capture_ty, iter_trait)))
1407+
},
1408+
ty::Array(elem_ty, _) | ty::Slice(elem_ty) => is_ty_with_side_effects(cx, *elem_ty, iter_trait),
1409+
ty::Tuple(field_tys) => field_tys
1410+
.iter()
1411+
.any(|field_ty| is_ty_with_side_effects(cx, field_ty, iter_trait)),
1412+
_ => false,
1413+
}
1414+
}
1415+
1416+
/// Check if `ty` is a mutable reference or equivalent. This includes:
1417+
/// - A mutable reference/pointer.
1418+
/// - A reference/pointer to a non-`Freeze` type.
1419+
/// - A `PhantomData` type containing any of the previous.
1420+
pub fn is_mutable_reference_or_equivalent<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
1421+
is_mutable_reference_or_equivalent_and(cx, ty, |_| true)
1422+
}
1423+
1424+
fn is_mutable_reference_or_equivalent_and<'tcx, F>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, pred: F) -> bool
1425+
where
1426+
F: Fn(Ty<'tcx>) -> bool,
1427+
{
1428+
match ty.kind() {
1429+
ty::RawPtr(ty, mutability) | ty::Ref(_, ty, mutability) => {
1430+
(mutability.is_mut() || !ty.is_freeze(cx.tcx, cx.typing_env())) && pred(*ty)
1431+
},
1432+
ty::Adt(adt_def, args) => adt_def.all_fields().any(|field| {
1433+
matches!(field.ty(cx.tcx, args).kind(), ty::Adt(adt_def, args) if adt_def.is_phantom_data() && args.types().any(|arg_ty| is_mutable_reference_or_equivalent(cx, arg_ty)))
1434+
}),
1435+
_ => false,
1436+
}
1437+
}

tests/ui/needless_collect.fixed

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,45 @@ fn bar<I: IntoIterator<Item = usize>>(_: Vec<usize>, _: I) {}
128128
fn baz<I: IntoIterator<Item = usize>>(_: I, _: (), _: impl IntoIterator<Item = char>) {}
129129

130130
mod issue9191 {
131+
use std::cell::Cell;
131132
use std::collections::HashSet;
133+
use std::hash::Hash;
134+
use std::marker::PhantomData;
135+
use std::ops::Deref;
132136

133-
fn foo(xs: Vec<i32>, mut ys: HashSet<i32>) {
137+
fn captures_ref_mut(xs: Vec<i32>, mut ys: HashSet<i32>) {
134138
if xs.iter().map(|x| ys.remove(x)).collect::<Vec<_>>().contains(&true) {
135139
todo!()
136140
}
137141
}
142+
143+
#[derive(Debug, Clone)]
144+
struct MyRef<'a>(PhantomData<&'a mut Cell<HashSet<i32>>>, *mut Cell<HashSet<i32>>);
145+
146+
impl MyRef<'_> {
147+
fn new(target: &mut Cell<HashSet<i32>>) -> Self {
148+
MyRef(PhantomData, target)
149+
}
150+
151+
fn get(&mut self) -> &mut Cell<HashSet<i32>> {
152+
unsafe { &mut *self.1 }
153+
}
154+
}
155+
156+
fn captures_phantom(xs: Vec<i32>, mut ys: Cell<HashSet<i32>>) {
157+
let mut ys_ref = MyRef::new(&mut ys);
158+
if xs
159+
.iter()
160+
.map({
161+
let mut ys_ref = ys_ref.clone();
162+
move |x| ys_ref.get().get_mut().remove(x)
163+
})
164+
.collect::<Vec<_>>()
165+
.contains(&true)
166+
{
167+
todo!()
168+
}
169+
}
138170
}
139171

140172
pub fn issue8055(v: impl IntoIterator<Item = i32>) -> Result<impl Iterator<Item = i32>, usize> {

tests/ui/needless_collect.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,45 @@ fn bar<I: IntoIterator<Item = usize>>(_: Vec<usize>, _: I) {}
128128
fn baz<I: IntoIterator<Item = usize>>(_: I, _: (), _: impl IntoIterator<Item = char>) {}
129129

130130
mod issue9191 {
131+
use std::cell::Cell;
131132
use std::collections::HashSet;
133+
use std::hash::Hash;
134+
use std::marker::PhantomData;
135+
use std::ops::Deref;
132136

133-
fn foo(xs: Vec<i32>, mut ys: HashSet<i32>) {
137+
fn captures_ref_mut(xs: Vec<i32>, mut ys: HashSet<i32>) {
134138
if xs.iter().map(|x| ys.remove(x)).collect::<Vec<_>>().contains(&true) {
135139
todo!()
136140
}
137141
}
142+
143+
#[derive(Debug, Clone)]
144+
struct MyRef<'a>(PhantomData<&'a mut Cell<HashSet<i32>>>, *mut Cell<HashSet<i32>>);
145+
146+
impl MyRef<'_> {
147+
fn new(target: &mut Cell<HashSet<i32>>) -> Self {
148+
MyRef(PhantomData, target)
149+
}
150+
151+
fn get(&mut self) -> &mut Cell<HashSet<i32>> {
152+
unsafe { &mut *self.1 }
153+
}
154+
}
155+
156+
fn captures_phantom(xs: Vec<i32>, mut ys: Cell<HashSet<i32>>) {
157+
let mut ys_ref = MyRef::new(&mut ys);
158+
if xs
159+
.iter()
160+
.map({
161+
let mut ys_ref = ys_ref.clone();
162+
move |x| ys_ref.get().get_mut().remove(x)
163+
})
164+
.collect::<Vec<_>>()
165+
.contains(&true)
166+
{
167+
todo!()
168+
}
169+
}
138170
}
139171

140172
pub fn issue8055(v: impl IntoIterator<Item = i32>) -> Result<impl Iterator<Item = i32>, usize> {

0 commit comments

Comments
 (0)