Expand mutable capture check for is_iter_with_side_effects()

This commit is contained in:
yanglsh 2025-04-01 20:14:59 +08:00
parent c6d76bb69d
commit a50e043d32
13 changed files with 202 additions and 128 deletions

View file

@ -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)
}
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 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)
}
false
/// 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,
}
}
let mut phantoms = FxHashSet::default();
has_non_owning_mutable_access_inner(cx, &mut phantoms, iter_ty)
}