@@ -2,11 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
2
2
use clippy_utils:: get_parent_as_impl;
3
3
use clippy_utils:: source:: snippet;
4
4
use clippy_utils:: ty:: { implements_trait, make_normalized_projection} ;
5
+ use rustc_ast:: Mutability ;
5
6
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { FnRetTy , ImplItemKind , ImplicitSelfKind , TyKind } ;
7
+ use rustc_hir:: { FnRetTy , ImplItemKind , ImplicitSelfKind , ItemKind , TyKind } ;
7
8
use rustc_lint:: { LateContext , LateLintPass } ;
9
+ use rustc_middle:: ty:: { self , Ty } ;
8
10
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9
- use rustc_span:: sym;
11
+ use rustc_span:: { sym, Symbol } ;
10
12
11
13
declare_clippy_lint ! {
12
14
/// ### What it does
@@ -46,7 +48,51 @@ declare_clippy_lint! {
46
48
pedantic,
47
49
"implementing `iter(_mut)` without an associated `IntoIterator for (&|&mut) Type` impl"
48
50
}
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 ] ) ;
50
96
51
97
/// Checks if a given type is nameable in a trait (impl).
52
98
/// 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 {
56
102
!matches ! ( ty. kind, TyKind :: OpaqueDef ( ..) )
57
103
}
58
104
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
+
59
119
impl 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
+
60
174
fn check_impl_item ( & mut self , cx : & LateContext < ' _ > , item : & rustc_hir:: ImplItem < ' _ > ) {
61
175
let item_did = item. owner_id . to_def_id ( ) ;
62
176
let ( borrow_prefix, expected_implicit_self) = match item. ident . name {
0 commit comments