Skip to content

Consider side effects when rewriting iterator behaviors #14490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

profetia
Copy link
Contributor

Closes #9191
Closes #14444
Closes #8055

Adds a new helper to partly check for side effects by recursively checking if the iterator type contains closures with mutable captures.

changelog: [double_ended_iterator_last] fix FP when iter has side effects
changelog: [needless_collect] fix lint not consider side effects

@rustbot
Copy link
Collaborator

rustbot commented Mar 28, 2025

r? @Jarcho

rustbot has assigned @Jarcho.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Mar 28, 2025
Copy link
Contributor

@Jarcho Jarcho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this needs to be limited to checking only closure captures. Any iterator that contains a mutable reference has the same issue.

I think any iterator type containing one of the following would need to not lint:

  • A mutable reference/pointer
  • A reference/pointer to a non-Freeze type
  • A PhantomData type containing any of the previous.

@profetia profetia force-pushed the issue14444 branch 2 times, most recently from 92e35bf to 4a54775 Compare April 1, 2025 12:17
@profetia
Copy link
Contributor Author

profetia commented Apr 1, 2025

Updated. Now these will also be covered.

@profetia profetia requested a review from Jarcho April 1, 2025 12:20
@profetia
Copy link
Contributor Author

profetia commented Apr 1, 2025

This CI failure does not seem to be my problem. Any ideas?

@samueltardieu
Copy link
Contributor

samueltardieu commented Apr 1, 2025

This CI failure does not seem to be my problem. Any ideas?

I've restarted it to see if it's intermittent (we'll have to fix it anyway) or not. For reference, here is the failing version.

Edit: this does not look like it is intermittent.

@profetia
Copy link
Contributor Author

profetia commented Apr 2, 2025

Thanks to #14514, the CI is working now.

@profetia
Copy link
Contributor Author

r? clippy

@rustbot rustbot assigned Manishearth and unassigned Jarcho Apr 17, 2025
@Manishearth
Copy link
Member

r? @samueltardieu

@rustbot
Copy link
Collaborator

rustbot commented Apr 17, 2025

samueltardieu is on vacation.

Please choose another assignee.

Copy link
Contributor

@Jarcho Jarcho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I didn't actually post a review last time I looked at this. Sorry about the wait.

@Manishearth
Copy link
Member

r? Jarcho

sending it back, then 😄

Comment on lines +1380 to +1398
/// Check if a Ty<'_> of `Iterator` has side effects when iterated over by checking if it
/// captures any mutable references or equivalents.
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 let ty::Adt(adt_def, args) = iter_ty.kind() {
return adt_def
.all_fields()
.any(|field| is_ty_with_side_effects(cx, field.ty(cx.tcx, args), iter_trait));
}

false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part still doesn't need to exist. There's no need for checking if the type is an iterator here; it's already known by all the lints.

Comment on lines +1400 to +1437
fn is_ty_with_side_effects<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, iter_trait: DefId) -> bool {
match ty.kind() {
ty::RawPtr(..) | ty::Ref(..) | ty::Adt(..) => is_mutable_reference_or_equivalent_and(cx, ty, |inner_ty| {
!implements_trait(cx, inner_ty, iter_trait, &[]) || is_iter_with_side_effects_impl(cx, inner_ty, iter_trait)
}),
ty::Closure(_, closure_args) => {
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)))
},
ty::Array(elem_ty, _) | ty::Slice(elem_ty) => is_ty_with_side_effects(cx, *elem_ty, iter_trait),
ty::Tuple(field_tys) => field_tys
.iter()
.any(|field_ty| is_ty_with_side_effects(cx, field_ty, iter_trait)),
_ => false,
}
}

/// Check if `ty` is a mutable reference or equivalent. This includes:
/// - A mutable reference/pointer.
/// - A reference/pointer to a non-`Freeze` type.
/// - A `PhantomData` type containing any of the previous.
pub fn is_mutable_reference_or_equivalent<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
is_mutable_reference_or_equivalent_and(cx, ty, |_| true)
}

fn is_mutable_reference_or_equivalent_and<'tcx, F>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, pred: F) -> bool
where
F: Fn(Ty<'tcx>) -> bool,
{
match ty.kind() {
ty::RawPtr(ty, mutability) | ty::Ref(_, ty, mutability) => {
(mutability.is_mut() || !ty.is_freeze(cx.tcx, cx.typing_env())) && pred(*ty)
},
ty::Adt(adt_def, args) => adt_def.all_fields().any(|field| {
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)))
}),
_ => false,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be merged. ADTs, closures, tuples etc. need to be treated the same at every level of nesting.

Comment on lines +1432 to +1434
ty::Adt(adt_def, args) => adt_def.all_fields().any(|field| {
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)))
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go back to being two arms, one to handle PhantomData specially, and one to handle every other ADT.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Apr 19, 2025
@Jarcho
Copy link
Contributor

Jarcho commented Apr 19, 2025

For the util name something like has_non_owning_mutable_access would be a more precise as to what it's checking for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
5 participants