Skip to content

Commit 4a54775

Browse files
committed
Expand mutable capture check for is_iter_with_side_effects()
1 parent a828bd5 commit 4a54775

File tree

3 files changed

+85
-5
lines changed

3 files changed

+85
-5
lines changed

clippy_utils/src/ty/mod.rs

+19-3
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 `ty` is an `Iterator` and 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;
@@ -1398,7 +1398,7 @@ fn is_iter_with_side_effects_impl<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx
13981398
captures
13991399
.tuple_fields()
14001400
.iter()
1401-
.any(|capture_ty| matches!(capture_ty.ref_mutability(), Some(Mutability::Mut)))
1401+
.any(|capture_ty| is_mutable_reference_or_equivalent(cx, capture_ty))
14021402
} else {
14031403
is_iter_with_side_effects_impl(cx, arg_ty, iter_trait)
14041404
}
@@ -1407,3 +1407,19 @@ fn is_iter_with_side_effects_impl<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx
14071407

14081408
false
14091409
}
1410+
1411+
/// Check if `ty` is a mutable reference or equivalent. This includes:
1412+
/// - A mutable reference/pointer.
1413+
/// - A reference/pointer to a non-`Freeze` type.
1414+
/// - A `PhantomData` type containing any of the previous.
1415+
pub fn is_mutable_reference_or_equivalent<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
1416+
match ty.kind() {
1417+
ty::RawPtr(ty, mutability) | ty::Ref(_, ty, mutability) => {
1418+
mutability.is_mut() || !ty.is_freeze(cx.tcx, cx.typing_env())
1419+
},
1420+
ty::Adt(adt_def, args) => adt_def.all_fields().any(|field| {
1421+
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)))
1422+
}),
1423+
_ => false,
1424+
}
1425+
}

tests/ui/needless_collect.fixed

+33-1
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

+33-1
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)