Skip to content

Add missing_fused_iterator pedantic lint - #17690

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

saberoueslati wants to merge 5 commits 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 2ff179f

Lint Added Removed Changed
clippy::missing_fused_iterator 243 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`",
);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, collapsed the early returns into a single if chain.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The help now points to the local impl Iterator. If no usable local impl span is available, it falls back to an unspanned help message.

/// 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? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd keep it pedantic, like iter_without_into_iter and missing_fields_in_debug. restriction doesn't fit: the lint doesn't discourage a language feature, it asks types to advertise a semantic guarantee. perf is the closer call, but Clippy can't prove the guarantee holds, and slapping FusedIterator on a resumable iterator would be wrong. Taking that false positive knowingly is what pedantic is for.

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..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

impl !FusedIterator for T {} is still unstable and produces E0658 on stable ([rust#68318](rust-lang/rust#68318)), so I don’t think the diagnostic should recommend code most users cannot compile. I kept the positive FusedIterator guidance and added #[allow(clippy::missing_fused_iterator)] for intentionally unfused iterators. The docs mention a negative impl as a nightly-only alternative.

Comment thread tests/ui/missing_fused_iterator.rs Outdated
//@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 Author

Choose a reason for hiding this comment

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

Done

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, grouped the test cases into modules so it's obvious which impls belong to which type.

Comment thread tests/ui/missing_fused_iterator.stderr Outdated
Comment on lines +16 to +30
error: this publicly reachable type implements `Iterator` but not `FusedIterator`
--> tests/ui/missing_fused_iterator.rs:24:5
|
LL | / pub enum PublicEnum {
LL | |
LL | | Empty,
LL | | }
| |_____^
|
help: if this iterator remains exhausted after returning `None`, consider implementing `FusedIterator`
--> tests/ui/missing_fused_iterator.rs:29:5
|
LL | impl Iterator for PublicEnum {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: otherwise, add `#[allow(clippy::missing_fused_iterator)]` with a comment saying why this iterator is not fused

@CommanderStorm CommanderStorm Sep 10, 2026 •

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 still find this diagnostic quite confusing.

I would very much prefer we skip nting where the type is defined, since I think this should be fairly obvious to the user.

error: publicly reachable type with `Iterator` implementation does not implement `FusedIterator`
  --> tests/ui/missing_fused_iterator.rs:24:5
   |
LL |     impl Iterator for PublicEnum {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  missing `FusedIterator` implementation
   |
note: public iterators that return `None` permanently after exhaustion should implement `FusedIterator` to enable compiler optimizations
help: implement `FusedIterator` for `PublicEnum`
   |
LL + impl std::iter::FusedIterator for PublicEnum {}
   |
help: if this iterator is intentionally not fused, suppress the lint
   |
LL + #[expect(clippy::missing_fused_iterator, reasoning = <under which circumstance the iterator can restart>)]
LL   impl Iterator for PublicEnum {

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The diagnostic now points at the impl Iterator header without mentioning the type definition. It suggests a FusedIterator implementation, preserving the generics, where clause, and cfg/cfg_attr attributes.

It also suggests #[expect(clippy::missing_fused_iterator, reason = "...")], falling back to #[allow] for MSRVs below 1.81. The attribute’s key is reason.

I made the note specific to Iterator::fuse becoming a no-op through library specialization. Since the lint now fires per implementation, #[allow]/#[expect] belong on the Iterator impl rather than the type.

@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.

Community review: LGTM from my side

View changes since this review

impl_span,
"publicly reachable type with `Iterator` implementation does not implement `FusedIterator`",
|diag| {
diag.span_label(impl_span, "missing `FusedIterator` implementation");

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.

🤔 Still a bit akward.
How about this?

Maybe even removing the label entirely, not sure.

Suggested change
diag.span_label(impl_span, "missing `FusedIterator` implementation");
diag.span_label(impl_span, "for this `Iterator`");

@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.

(meant to +1 instead)

View changes since this review

@rustbot

rustbot commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (possibly #13598) made this pull request unmergeable. Please resolve the merge conflicts.

This branch has not been deployed

No deployments
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