Skip to content

Conversation

@cuongleqq
Copy link
Contributor

@cuongleqq cuongleqq commented Dec 12, 2025

Fixes #16061.

Extend never_loop to also lint iterator reduction methods (e.g. for_each, try_for_each, fold, try_fold, reduce, all, any) when the closure’s body diverges (return type !). Add a UI test never_loop_iterator_reduction to cover these cases.

Testing:

  • TESTNAME=never_loop_iterator_reduction cargo uitest

changelog: [never_loop]: lint diverging iterator reduction closures like for_each and fold

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Dec 12, 2025
@rustbot
Copy link
Collaborator

rustbot commented Dec 12, 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

Comment on lines 893 to 901
if let ExprKind::MethodCall(path, recv, args, _) = expr.kind
&& matches!(
path.ident.name,
sym::for_each | sym::try_for_each | sym::fold | sym::try_fold | sym::reduce | sym::all | sym::any
)
&& ty::get_iterator_item_ty(cx, cx.typeck_results().expr_ty(recv)).is_some()
{
never_loop::check_iterator_reduction(cx, expr, recv, args);
}
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 be combined with the previous method check as something like

if let ExprKind::MethodCall(path, recv, args, _) = expr.kind {
  match (path.ident.name, args) {
    // methods here
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used a single MethodCall destructure as suggested, but I kept 2 if branches instead of a single match because some methods like for_each, all, any are handled by both lints. Match will short-circuit to only the first matching arm.

Copy link
Contributor

Choose a reason for hiding this comment

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

Still make it a single match. A match arm can do multiple things.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok! I switched it to a single match as suggested. For the overlapping methods, the match arm now calls both unused_enumerate_index and never_loop. Other arms handle the single-lint cases.

expr.span,
"this iterator reduction never loops (closure always diverges)",
|diag| {
let mut app = Applicability::Unspecified;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be WithPlaceholders

"this iterator reduction never loops (closure always diverges)",
|diag| {
let mut app = Applicability::Unspecified;
let recv_snip = make_iterator_snippet(cx, recv, &mut app);
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 just be snippet_with_context. We already know the receiver is an iterator.

path.ident.name,
sym::for_each | sym::try_for_each | sym::fold | sym::try_fold | sym::reduce | sym::all | sym::any
)
&& ty::get_iterator_item_ty(cx, cx.typeck_results().expr_ty(recv)).is_some()
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 be recv.ty_based_def(cx).assoc_fn_parent(cx).is_diag_item(cx, sym::Iterator).

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Dec 13, 2025
@rustbot
Copy link
Collaborator

rustbot commented Dec 13, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label Dec 13, 2025
@cuongleqq
Copy link
Contributor Author

I pushed an update to address all 4 comments

  • Combined the method check for both unused_enumerate_index and never_loop
  • Use a more direct check for Iterator trait
  • Use HasPlaceholders for the applicability
  • Use snippet_with_context instead of make_iterator_snippet

@rustbot ready

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

Addressed the requested changes:

  • Switched to a single match for the MethodCall handling

@rustbot ready

Comment on lines 82 to 98
// identify which argument is the closure based on the method kind
let Some(method_name) = (match expr.kind {
ExprKind::MethodCall(path, ..) => Some(path.ident.name),
_ => None,
}) else {
return;
};

let closure_arg = match method_name {
sym::for_each | sym::try_for_each | sym::reduce | sym::all | sym::any => args.first(),
sym::fold | sym::try_fold => args.get(1),
_ => None,
};

let Some(closure_arg) = closure_arg else {
return;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

You should be splitting this before getting to this function.

Comment on lines 894 to 897
unused_enumerate_index::check_method(cx, expr, recv, arg);
if is_iterator_method {
never_loop::check_iterator_reduction(cx, expr, recv, args);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you pull out the checks shared between the two lints to happen here. Both the check for ExprKind::Closure and the iterator check are the same for both of them.

Comment on lines 887 to 890
let is_iterator_method = cx
.ty_based_def(expr)
.assoc_fn_parent(cx)
.is_diag_item(cx, sym::Iterator);
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 be delayed until after the name check. Type checking and item identification is notably slower than matching on a symbol.

Defining a closure would be fine.

@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 Dec 14, 2025
@cuongleqq
Copy link
Contributor Author

Thanks for the quick review!

I pushed an update to address the comments:

  • is_iterator_method() stays behind the cheap name/arg checks
  • moved the shared iterator + closure checks into check_expr
  • pass the closure into never_loop / unused_enumerate_index instead of re-parsing inside

@rustbot ready

@Jarcho: I’m repeating if let ExprKind::Closure(closure) = arg.kind && is_iterator_method() in all match arms to keep the expensive check last. Do you want me to make a small helper for this, or is it fine as-is?

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Dec 15, 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.

@Jarcho Jarcho added this pull request to the merge queue Dec 15, 2025
@Jarcho
Copy link
Contributor

Jarcho commented Dec 15, 2025

I’m repeating if let ExprKind::Closure(closure) = arg.kind && is_iterator_method() in all match arms to keep the expensive check last. Do you want me to make a small helper for this, or is it fine as-is?

A helper function would end up harder to read. It's two steps that are, without external context, unrelated to each other. Having to jump to another spot to just to read what it does is also a pain.

Merged via the queue into rust-lang:master with commit d36794f Dec 15, 2025
11 checks passed
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Dec 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

never_loop: Also check all known iterator reductions.

3 participants