@@ -2,11 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
22use clippy_utils:: get_parent_as_impl;
33use clippy_utils:: source:: snippet;
44use clippy_utils:: ty:: { implements_trait, make_normalized_projection} ;
5+ use rustc_ast:: Mutability ;
56use rustc_errors:: Applicability ;
6- use rustc_hir:: { FnRetTy , ImplItemKind , ImplicitSelfKind , TyKind } ;
7+ use rustc_hir:: { FnRetTy , ImplItemKind , ImplicitSelfKind , ItemKind , TyKind } ;
78use rustc_lint:: { LateContext , LateLintPass } ;
9+ use rustc_middle:: ty:: { self , Ty } ;
810use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9- use rustc_span:: sym;
11+ use rustc_span:: { sym, Symbol } ;
1012
1113declare_clippy_lint ! {
1214 /// ### What it does
@@ -46,7 +48,51 @@ declare_clippy_lint! {
4648 pedantic,
4749 "implementing `iter(_mut)` without an associated `IntoIterator for (&|&mut) Type` impl"
4850}
49- declare_lint_pass ! ( IterWithoutIntoIter => [ ITER_WITHOUT_INTO_ITER ] ) ;
51+
52+ declare_clippy_lint ! {
53+ /// ### What it does
54+ /// This is the opposite of the `iter_without_into_iter` lint.
55+ /// It looks for `IntoIterator for (&|&mut) Type` implementations without an inherent `iter` or `iter_mut` method.
56+ ///
57+ /// ### Why is this bad?
58+ /// It's not bad, but having them is idiomatic and allows the type to be used in iterator chains
59+ /// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).iter()` syntax
60+ /// in case of ambiguity with another `Intoiterator` impl.
61+ ///
62+ /// ### Example
63+ /// ```rust
64+ /// struct MySlice<'a>(&'a [u8]);
65+ /// impl<'a> IntoIterator for &MySlice<'a> {
66+ /// type Item = &'a u8;
67+ /// type IntoIter = std::slice::Iter<'a, u8>;
68+ /// fn into_iter(self) -> Self::IntoIter {
69+ /// self.0.iter()
70+ /// }
71+ /// }
72+ /// ```
73+ /// Use instead:
74+ /// ```rust
75+ /// struct MySlice<'a>(&'a [u8]);
76+ /// impl<'a> MySlice<'a> {
77+ /// pub fn iter(&self) -> std::slice::Iter<'a, u8> {
78+ /// self.into_iter()
79+ /// }
80+ /// }
81+ /// impl<'a> IntoIterator for &MySlice<'a> {
82+ /// type Item = &'a u8;
83+ /// type IntoIter = std::slice::Iter<'a, u8>;
84+ /// fn into_iter(self) -> Self::IntoIter {
85+ /// self.0.iter()
86+ /// }
87+ /// }
88+ /// ```
89+ #[ clippy:: version = "1.74.0" ]
90+ pub INTO_ITER_WITHOUT_ITER ,
91+ pedantic,
92+ "implementing `IntoIterator for (&|&mut) Type` without an inherent `iter(_mut)` method"
93+ }
94+
95+ declare_lint_pass ! ( IterWithoutIntoIter => [ ITER_WITHOUT_INTO_ITER , INTO_ITER_WITHOUT_ITER ] ) ;
5096
5197/// Checks if a given type is nameable in a trait (impl).
5298/// RPIT is stable, but impl Trait in traits is not (yet), so when we have
@@ -56,7 +102,75 @@ fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
56102 !matches ! ( ty. kind, TyKind :: OpaqueDef ( ..) )
57103}
58104
105+ fn type_has_inherent_method ( cx : & LateContext < ' _ > , ty : Ty < ' _ > , method_name : Symbol ) -> bool {
106+ if let Some ( ty_did) = ty. ty_adt_def ( ) . map ( ty:: AdtDef :: did) {
107+ cx. tcx . inherent_impls ( ty_did) . iter ( ) . any ( |& did| {
108+ cx. tcx
109+ . associated_items ( did)
110+ . filter_by_name_unhygienic ( method_name)
111+ . next ( )
112+ . is_some_and ( |item| item. kind == ty:: AssocKind :: Fn )
113+ } )
114+ } else {
115+ false
116+ }
117+ }
118+
59119impl LateLintPass < ' _ > for IterWithoutIntoIter {
120+ fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & rustc_hir:: Item < ' _ > ) {
121+ if let ItemKind :: Impl ( imp) = item. kind
122+ && let TyKind :: Ref ( _, self_ty_without_ref) = & imp. self_ty . kind
123+ && let Some ( trait_ref) = imp. of_trait
124+ && trait_ref. trait_def_id ( ) . is_some_and ( |did| cx. tcx . is_diagnostic_item ( sym:: IntoIterator , did) )
125+ && let & ty:: Ref ( _, ty, mtbl) = cx. tcx . type_of ( item. owner_id ) . instantiate_identity ( ) . kind ( )
126+ && let expected_method_name = match mtbl {
127+ Mutability :: Mut => sym:: iter_mut,
128+ Mutability :: Not => sym:: iter,
129+ }
130+ && !type_has_inherent_method ( cx, ty, expected_method_name)
131+ && let Some ( iter_assoc_span) = imp. items . iter ( ) . find_map ( |item| {
132+ if item. ident . name == sym ! ( IntoIter ) {
133+ Some ( cx. tcx . hir ( ) . impl_item ( item. id ) . expect_type ( ) . span )
134+ } else {
135+ None
136+ }
137+ } )
138+ {
139+ span_lint_and_then (
140+ cx,
141+ INTO_ITER_WITHOUT_ITER ,
142+ item. span ,
143+ & format ! ( "`IntoIterator` implemented for a reference type without an `{expected_method_name}` method" ) ,
144+ |diag| {
145+ // The suggestion forwards to the `IntoIterator` impl and uses a form of UFCS
146+ // to avoid name ambiguities, as there might be an inherent into_iter method
147+ // that we don't want to call.
148+ let sugg = format ! (
149+ "
150+ impl {self_ty_without_ref} {{
151+ fn {expected_method_name}({ref_self}self) -> {iter_ty} {{
152+ <{ref_self}Self as IntoIterator>::into_iter(self)
153+ }}
154+ }}
155+ " ,
156+ self_ty_without_ref = snippet( cx, self_ty_without_ref. ty. span, ".." ) ,
157+ ref_self = mtbl. ref_prefix_str( ) ,
158+ iter_ty = snippet( cx, iter_assoc_span, ".." ) ,
159+ ) ;
160+
161+ diag. span_suggestion_verbose (
162+ item. span . shrink_to_lo ( ) ,
163+ format ! ( "consider implementing `{expected_method_name}`" ) ,
164+ sugg,
165+ // Just like iter_without_into_iter, this suggestion is on a best effort basis
166+ // and requires potentially adding lifetimes or moving them around.
167+ Applicability :: Unspecified
168+ ) ;
169+ }
170+ ) ;
171+ }
172+ }
173+
60174 fn check_impl_item ( & mut self , cx : & LateContext < ' _ > , item : & rustc_hir:: ImplItem < ' _ > ) {
61175 let item_did = item. owner_id . to_def_id ( ) ;
62176 let ( borrow_prefix, expected_implicit_self) = match item. ident . name {
0 commit comments