Skip to content

Add missing_fused_iterator pedantic lint - #17690

Open
saberoueslati wants to merge 1 commit into
rust-lang:masterfrom
saberoueslati:new-lint/missing-fused-iterator
Open

Add missing_fused_iterator pedantic lint#17690
saberoueslati wants to merge 1 commit into
rust-lang:masterfrom
saberoueslati:new-lint/missing-fused-iterator

Conversation

@saberoueslati

@saberoueslati saberoueslati commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Adds clippy::missing_fused_iterator, an MSRV-aware pedantic lint for publicly reachable structs, enums, and unions that have a positive Iterator impl but no FusedIterator impl.

Fixes #17608

The lint uses is_reachable rather than the more common is_exported, so it also covers types leaked through public signatures. Any FusedIterator impl suppresses it, including a conditional or negative one. I deliberately left two cases out: impls on a reference to the type, and types reachable only through RPIT.

I filed it as pedantic because Clippy cannot prove an iterator stays exhausted. If your iterator resumes on purpose, allow the lint and say so in the docs.

  • Followed lint naming conventions
  • Added passing UI tests (including committed .stderr file)
  • cargo test passes locally
  • Executed cargo dev update_lints
  • Added lint documentation
  • Run cargo dev fmt

changelog: new lint: [missing_fused_iterator]

Used an LLM to help me understand the issue and as an extra reviewer for my work.

@rustbot rustbot added the S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. label Sep 6, 2026
@rustbot

rustbot commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request. A reviewer will take a look after it receives 2 community reviews.

In the meantime, we would highly appreciate if you could try to review any of PRs waiting on community reviews.

@rustbot rustbot added needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Lintcheck changes for 5c030c0

Lint Added Removed Changed
clippy::missing_fused_iterator 223 0 0

This comment will be updated if you push new changes

@CommanderStorm CommanderStorm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mostly LGTM, but please attach the span. pointing to a struct and saying "this iterator ..." is just very confusing 😉

View changes since this review

Comment on lines +75 to +122
if !matches!(
item.kind,
ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)
) || !span_is_local(item.span)
|| !cx.effective_visibilities.is_reachable(item.owner_id.def_id)
|| is_lint_allowed(cx, MISSING_FUSED_ITERATOR, item.hir_id())
{
return;
}

let Some(iterator_trait) = cx.tcx.lang_items().iterator_trait() else {
return;
};
let Some(fused_iterator_trait) = cx.tcx.lang_items().fused_iterator_trait() else {
return;
};
if !self.msrv.is_stable(cx, fused_iterator_trait) {
return;
}

let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip();
if !cx
.tcx
.non_blanket_impls_for_ty(iterator_trait, ty)
.any(|impl_id| cx.tcx.impl_polarity(impl_id) == ty::ImplPolarity::Positive)
{
return;
}

// Any implementation, including a conditional or negative one, indicates that the
// `FusedIterator` status of this nominal type has been considered explicitly.
if cx
.tcx
.non_blanket_impls_for_ty(fused_iterator_trait, ty)
.next()
.is_some()
{
return;
}

span_lint_and_help(
cx,
MISSING_FUSED_ITERATOR,
item.span,
"this publicly reachable type implements `Iterator` but not `FusedIterator`",
None,
"if this iterator remains exhausted after returning `None`, consider implementing `FusedIterator`",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the "usual" way to do this is by an long if-chain instead of returning, or?

Suggested change
if !matches!(
item.kind,
ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)
) || !span_is_local(item.span)
|| !cx.effective_visibilities.is_reachable(item.owner_id.def_id)
|| is_lint_allowed(cx, MISSING_FUSED_ITERATOR, item.hir_id())
{
return;
}
let Some(iterator_trait) = cx.tcx.lang_items().iterator_trait() else {
return;
};
let Some(fused_iterator_trait) = cx.tcx.lang_items().fused_iterator_trait() else {
return;
};
if !self.msrv.is_stable(cx, fused_iterator_trait) {
return;
}
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip();
if !cx
.tcx
.non_blanket_impls_for_ty(iterator_trait, ty)
.any(|impl_id| cx.tcx.impl_polarity(impl_id) == ty::ImplPolarity::Positive)
{
return;
}
// Any implementation, including a conditional or negative one, indicates that the
// `FusedIterator` status of this nominal type has been considered explicitly.
if cx
.tcx
.non_blanket_impls_for_ty(fused_iterator_trait, ty)
.next()
.is_some()
{
return;
}
span_lint_and_help(
cx,
MISSING_FUSED_ITERATOR,
item.span,
"this publicly reachable type implements `Iterator` but not `FusedIterator`",
None,
"if this iterator remains exhausted after returning `None`, consider implementing `FusedIterator`",
);
let kind_can_impl_iter = matches!(
item.kind,
ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)
);
if kind_can_impl_iter
&& span_is_local(item.span)
&& cx.effective_visibilities.is_reachable(item.owner_id.def_id)
&& !is_lint_allowed(cx, MISSING_FUSED_ITERATOR, item.hir_id())
&& let Some(iterator_trait) = cx.tcx.lang_items().iterator_trait()
&& let Some(fused_iterator_trait) = cx.tcx.lang_items().fused_iterator_trait()
&& self.msrv.is_stable(cx, fused_iterator_trait)
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip()
&& cx
.tcx
.non_blanket_impls_for_ty(iterator_trait, ty)
.any(|impl_id| cx.tcx.impl_polarity(impl_id) == ty::ImplPolarity::Positive)
// Any implementation, including a conditional or negative one, indicates that the
// `FusedIterator` status of this nominal type has been considered explicitly.
&& cx
.tcx
.non_blanket_impls_for_ty(fused_iterator_trait, ty)
.next()
.is_none() {
span_lint_and_help(
cx,
MISSING_FUSED_ITERATOR,
item.span,
"this publicly reachable type implements `Iterator` but not `FusedIterator`",
None,
"if this iterator remains exhausted after returning `None`, consider implementing `FusedIterator`",
);
}

MISSING_FUSED_ITERATOR,
item.span,
"this publicly reachable type implements `Iterator` but not `FusedIterator`",
None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we suggest the span of the impl Iterator here? I think it would be fairly handy if for example the users do "cursed things" such as putting the iterator impls into different files or such.

/// this lint, even if its generic bounds do not match every `Iterator` implementation.
#[clippy::version = "1.100.0"]
pub MISSING_FUSED_ITERATOR,
pedantic,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should this be restriction, perf or pedantic?

Given that this does restrict the syntax in order to improve perf for users who want, I feel like pedantic might not be the right choice, or? 🤔

item.span,
"this publicly reachable type implements `Iterator` but not `FusedIterator`",
None,
"if this iterator remains exhausted after returning `None`, consider implementing `FusedIterator`",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should add suggestions for both

  • impl !FusedIterator for IteratableThing {};
  • impl FusedIterator for IteratableThing {};

negative polarity might not be something that the wider userbase thinks about when reading this lint..

//@no-rustfix
//@aux-build:proc_macros.rs

#![allow(dead_code)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#![allow(dead_code)]
#![expect(dead_code)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: there are a lot of impls. To keep this more "tidy", could you use one module per testcase to wrap this up nicely so that it is clearer what impl belongs to what check and such?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing FusedIterator implementations

3 participants