diff --git a/CHANGELOG.md b/CHANGELOG.md index 471163499e52..55757117077b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5360,6 +5360,7 @@ Released 2018-09-13 [`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc [`missing_fields_in_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_fields_in_debug [`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items +[`missing_iterator_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_iterator_fold [`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc [`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 23d0983151a9..e8107aaeb342 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -488,6 +488,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO, crate::missing_fields_in_debug::MISSING_FIELDS_IN_DEBUG_INFO, crate::missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS_INFO, + crate::missing_iterator_fold::MISSING_ITERATOR_FOLD_INFO, crate::missing_trait_methods::MISSING_TRAIT_METHODS_INFO, crate::mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION_INFO, crate::mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 7da916ade2ab..ec74870b8b05 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -222,6 +222,7 @@ mod missing_doc; mod missing_enforced_import_rename; mod missing_fields_in_debug; mod missing_inline; +mod missing_iterator_fold; mod missing_trait_methods; mod mixed_read_write_in_expression; mod module_style; @@ -1105,6 +1106,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }); store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv()))); store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); + store.register_late_pass(|_| Box::new(missing_iterator_fold::MissingIteratorFold)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/missing_iterator_fold.rs b/clippy_lints/src/missing_iterator_fold.rs new file mode 100644 index 000000000000..a2624e13cdd1 --- /dev/null +++ b/clippy_lints/src/missing_iterator_fold.rs @@ -0,0 +1,83 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir::{AssocItemKind, Impl, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::declare_lint_pass; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `Iterator` implementations not specializing `fold`. + /// + /// ### Why is this bad? + /// Specialize `fold` might provide more performant internal iteration. + /// Methods relying on `fold` would then be faster as well. + /// By default, methods that consume the entire iterator rely on it such as `for_each`, `count`... + /// + /// ### Example + /// ```no_run + /// struct MyIter(u32); + /// + /// impl Iterator for MyIter { + /// type Item = u32; + /// fn next(&mut self) -> Option { + /// # todo!() + /// // ... + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// struct MyIter(u32); + /// + /// impl Iterator for MyIter { + /// type Item = u32; + /// fn next(&mut self) -> Option { + /// # todo!() + /// // ... + /// } + /// fn fold(self, init: B, f: F) -> B + /// where + /// F: FnMut(B, Self::Item) -> B, + /// { + /// todo!() + /// } + /// } + /// ``` + #[clippy::version = "1.77.0"] + pub MISSING_ITERATOR_FOLD, + restriction, + "a missing `Iterator::fold` specialization" +} + +declare_lint_pass!(MissingIteratorFold => [MISSING_ITERATOR_FOLD]); + +impl LateLintPass<'_> for MissingIteratorFold { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if in_external_macro(cx.sess(), item.span) { + return; + } + if let ItemKind::Impl(Impl { + of_trait: Some(trait_ref), + .. + }) = &item.kind + && let Some(trait_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::Iterator, trait_id) + { + let has_fold = item + .expect_impl() + .items + .iter() + .filter(|assoc| matches!(assoc.kind, AssocItemKind::Fn { .. })) + .any(|assoc| assoc.ident.name.as_str() == "fold"); + if !has_fold { + span_lint( + cx, + MISSING_ITERATOR_FOLD, + item.span, + "you are implementing `Iterator` without specializing its `fold` method", + ); + } + } + } +} diff --git a/tests/ui/missing_iterator_fold.rs b/tests/ui/missing_iterator_fold.rs new file mode 100644 index 000000000000..a4136012f0f8 --- /dev/null +++ b/tests/ui/missing_iterator_fold.rs @@ -0,0 +1,30 @@ +#![warn(clippy::missing_iterator_fold)] + +struct Countdown(u8); + +impl Iterator for Countdown { + type Item = u8; + + fn next(&mut self) -> Option { + self.0 = self.0.checked_sub(1)?; + Some(self.0) + } +} + +struct Countdown2(u8); + +impl Iterator for Countdown2 { + type Item = u8; + + fn next(&mut self) -> Option { + self.0 = self.0.checked_sub(1)?; + Some(self.0) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + (0..self.0).rfold(init, f) + } +} diff --git a/tests/ui/missing_iterator_fold.stderr b/tests/ui/missing_iterator_fold.stderr new file mode 100644 index 000000000000..88c264801e5a --- /dev/null +++ b/tests/ui/missing_iterator_fold.stderr @@ -0,0 +1,17 @@ +error: you are implementing `Iterator` without specializing its `fold` method + --> $DIR/missing_iterator_fold.rs:5:1 + | +LL | / impl Iterator for Countdown { +LL | | type Item = u8; +LL | | +LL | | fn next(&mut self) -> Option { +... | +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::missing-iterator-fold` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_iterator_fold)]` + +error: aborting due to 1 previous error +