Skip to content

Commit 0e43a04

Browse files
committed
Auto merge of #11587 - y21:into_iter_without_iter, r=Jarcho
new lint: `into_iter_without_iter` Closes #9736 (part 2) This implements the other lint that my earlier PR missed: given an `IntoIterator for &Type` impl, check that there exists an inherent `fn iter(&self)` method. changelog: new lint: `into_iter_without_iter` r? `@Jarcho` since you reviewed #11527 I figured it makes sense for you to review this as well?
2 parents b00236d + 8eb586d commit 0e43a04

File tree

5 files changed

+357
-3
lines changed

5 files changed

+357
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5011,6 +5011,7 @@ Released 2018-09-13
50115011
[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
50125012
[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
50135013
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
5014+
[`into_iter_without_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_without_iter
50145015
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
50155016
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
50165017
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
229229
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
230230
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
231231
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
232+
crate::iter_without_into_iter::INTO_ITER_WITHOUT_ITER_INFO,
232233
crate::iter_without_into_iter::ITER_WITHOUT_INTO_ITER_INFO,
233234
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
234235
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,

clippy_lints/src/iter_without_into_iter.rs

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::get_parent_as_impl;
33
use clippy_utils::source::snippet;
44
use clippy_utils::ty::{implements_trait, make_normalized_projection};
5+
use rustc_ast::Mutability;
56
use rustc_errors::Applicability;
6-
use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, TyKind};
7+
use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, ItemKind, TyKind};
78
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::{self, Ty};
810
use rustc_session::{declare_lint_pass, declare_tool_lint};
9-
use rustc_span::sym;
11+
use rustc_span::{sym, Symbol};
1012

1113
declare_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+
59119
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+
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 {

tests/ui/into_iter_without_iter.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//@no-rustfix
2+
#![warn(clippy::into_iter_without_iter)]
3+
4+
use std::iter::IntoIterator;
5+
6+
fn main() {
7+
{
8+
struct S;
9+
10+
impl<'a> IntoIterator for &'a S {
11+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
12+
type IntoIter = std::slice::Iter<'a, u8>;
13+
type Item = &'a u8;
14+
fn into_iter(self) -> Self::IntoIter {
15+
todo!()
16+
}
17+
}
18+
impl<'a> IntoIterator for &'a mut S {
19+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
20+
type IntoIter = std::slice::IterMut<'a, u8>;
21+
type Item = &'a mut u8;
22+
fn into_iter(self) -> Self::IntoIter {
23+
todo!()
24+
}
25+
}
26+
}
27+
{
28+
struct S<T>(T);
29+
impl<'a, T> IntoIterator for &'a S<T> {
30+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
31+
type IntoIter = std::slice::Iter<'a, T>;
32+
type Item = &'a T;
33+
fn into_iter(self) -> Self::IntoIter {
34+
todo!()
35+
}
36+
}
37+
impl<'a, T> IntoIterator for &'a mut S<T> {
38+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
39+
type IntoIter = std::slice::IterMut<'a, T>;
40+
type Item = &'a mut T;
41+
fn into_iter(self) -> Self::IntoIter {
42+
todo!()
43+
}
44+
}
45+
}
46+
{
47+
// Both iter and iter_mut methods exist, don't lint
48+
struct S<'a, T>(&'a T);
49+
50+
impl<'a, T> S<'a, T> {
51+
fn iter(&self) -> std::slice::Iter<'a, T> {
52+
todo!()
53+
}
54+
fn iter_mut(&mut self) -> std::slice::IterMut<'a, T> {
55+
todo!()
56+
}
57+
}
58+
59+
impl<'a, T> IntoIterator for &S<'a, T> {
60+
type IntoIter = std::slice::Iter<'a, T>;
61+
type Item = &'a T;
62+
fn into_iter(self) -> Self::IntoIter {
63+
todo!()
64+
}
65+
}
66+
67+
impl<'a, T> IntoIterator for &mut S<'a, T> {
68+
type IntoIter = std::slice::IterMut<'a, T>;
69+
type Item = &'a mut T;
70+
fn into_iter(self) -> Self::IntoIter {
71+
todo!()
72+
}
73+
}
74+
}
75+
{
76+
// Only `iter` exists, no `iter_mut`
77+
struct S<'a, T>(&'a T);
78+
79+
impl<'a, T> S<'a, T> {
80+
fn iter(&self) -> std::slice::Iter<'a, T> {
81+
todo!()
82+
}
83+
}
84+
85+
impl<'a, T> IntoIterator for &S<'a, T> {
86+
type IntoIter = std::slice::Iter<'a, T>;
87+
type Item = &'a T;
88+
fn into_iter(self) -> Self::IntoIter {
89+
todo!()
90+
}
91+
}
92+
93+
impl<'a, T> IntoIterator for &mut S<'a, T> {
94+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
95+
type IntoIter = std::slice::IterMut<'a, T>;
96+
type Item = &'a mut T;
97+
fn into_iter(self) -> Self::IntoIter {
98+
todo!()
99+
}
100+
}
101+
}
102+
{
103+
// `iter` exists, but `IntoIterator` is implemented for an alias. inherent_impls doesn't "normalize"
104+
// aliases so that `inherent_impls(Alias)` where `type Alias = S` returns nothing, so this can lead
105+
// to fun FPs. Make sure it doesn't happen here (we're using type_of, which should skip the alias).
106+
struct S;
107+
108+
impl S {
109+
fn iter(&self) -> std::slice::Iter<'static, u8> {
110+
todo!()
111+
}
112+
}
113+
114+
type Alias = S;
115+
116+
impl IntoIterator for &Alias {
117+
type IntoIter = std::slice::Iter<'static, u8>;
118+
type Item = &'static u8;
119+
fn into_iter(self) -> Self::IntoIter {
120+
todo!()
121+
}
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)