Skip to content

Commit f8409ef

Browse files
committed
Auto merge of #11696 - y21:iter_without_into_iter_suggestion, r=xFrednet
[`iter_without_into_iter`]: fix papercuts in suggestion and restrict linting to exported types See #11692 for more context. tldr: the lint `iter_without_into_iter` has suggestions that don't compile, which imo isn't that problematic because it does have the appropriate `Applicability` that tells external tools that it shouldn't be auto-applied. However there were some obvious "errors" in the suggestion that really should've been included in my initial PR adding the lint, which is fixed by this PR: - `IntoIterator::into_iter` needs a `self` argument. - `IntoIterator::Iter` associated type doesn't exist. This should've just been `Item`. This still doesn't make it machine applicable, and the remaining things are imho quite non-trivial to implement, as I've explained in rust-lang/rust-clippy#11692 (comment). I personally think it's fine to leave it there and let the user change the remaining errors when copy-pasting the suggestion (e.g. errors caused by lifetimes that were permitted in fn return-position but are not in associated types). This is how many of our other lint suggestions already work. Also, we now restrict linting to only exported types. This required moving basically all of the tests around since they were previously in the `main` function. Same for `into_iter_without_iter`. The git diff is a bit useless here... changelog: [`iter_without_into_iter`]: fix papercuts in suggestion and restrict linting to exported types (cc `@lopopolo,` figured I should mention you since you created the issue)
2 parents 2f0f4dd + 9a10d32 commit f8409ef

File tree

5 files changed

+358
-343
lines changed

5 files changed

+358
-343
lines changed

clippy_lints/src/iter_without_into_iter.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ declare_clippy_lint! {
1919
/// It's not bad, but having them is idiomatic and allows the type to be used in for loops directly
2020
/// (`for val in &iter {}`), without having to first call `iter()` or `iter_mut()`.
2121
///
22+
/// ### Limitations
23+
/// This lint focuses on providing an idiomatic API. Therefore, it will only
24+
/// lint on types which are accessible outside of the crate. For internal types,
25+
/// the `IntoIterator` trait can be implemented on demand if it is actually needed.
26+
///
2227
/// ### Example
2328
/// ```no_run
2429
/// struct MySlice<'a>(&'a [u8]);
@@ -61,6 +66,14 @@ declare_clippy_lint! {
6166
/// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).into_iter()` syntax
6267
/// in case of ambiguity with another `IntoIterator` impl.
6368
///
69+
/// ### Limitations
70+
/// This lint focuses on providing an idiomatic API. Therefore, it will only
71+
/// lint on types which are accessible outside of the crate. For internal types,
72+
/// these methods can be added on demand if they are actually needed. Otherwise,
73+
/// it would trigger the [`dead_code`] lint for the unused method.
74+
///
75+
/// [`dead_code`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#dead-code
76+
///
6477
/// ### Example
6578
/// ```no_run
6679
/// struct MySlice<'a>(&'a [u8]);
@@ -104,6 +117,12 @@ fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
104117
!matches!(ty.kind, TyKind::OpaqueDef(..))
105118
}
106119

120+
fn is_ty_exported(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
121+
ty.ty_adt_def()
122+
.and_then(|adt| adt.did().as_local())
123+
.is_some_and(|did| cx.effective_visibilities.is_exported(did))
124+
}
125+
107126
/// Returns the deref chain of a type, starting with the type itself.
108127
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
109128
iter::successors(Some(ty), |&ty| {
@@ -154,6 +173,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
154173
None
155174
}
156175
})
176+
&& is_ty_exported(cx, ty)
157177
{
158178
span_lint_and_then(
159179
cx,
@@ -226,6 +246,7 @@ impl {self_ty_without_ref} {{
226246
)
227247
// Only lint if the `IntoIterator` impl doesn't actually exist
228248
&& !implements_trait(cx, ref_ty, into_iter_did, &[])
249+
&& is_ty_exported(cx, ref_ty.peel_refs())
229250
{
230251
let self_ty_snippet = format!("{borrow_prefix}{}", snippet(cx, imp.self_ty.span, ".."));
231252

@@ -247,8 +268,8 @@ impl {self_ty_without_ref} {{
247268
"
248269
impl IntoIterator for {self_ty_snippet} {{
249270
type IntoIter = {ret_ty};
250-
type Iter = {iter_ty};
251-
fn into_iter() -> Self::IntoIter {{
271+
type Item = {iter_ty};
272+
fn into_iter(self) -> Self::IntoIter {{
252273
self.iter()
253274
}}
254275
}}

tests/ui/into_iter_without_iter.rs

Lines changed: 96 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,127 +3,117 @@
33

44
use std::iter::IntoIterator;
55

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-
}
6+
pub struct S1;
7+
impl<'a> IntoIterator for &'a S1 {
8+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
9+
type IntoIter = std::slice::Iter<'a, u8>;
10+
type Item = &'a u8;
11+
fn into_iter(self) -> Self::IntoIter {
12+
todo!()
2613
}
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-
}
14+
}
15+
impl<'a> IntoIterator for &'a mut S1 {
16+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
17+
type IntoIter = std::slice::IterMut<'a, u8>;
18+
type Item = &'a mut u8;
19+
fn into_iter(self) -> Self::IntoIter {
20+
todo!()
4521
}
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-
}
22+
}
5823

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-
}
24+
pub struct S2<T>(T);
25+
impl<'a, T> IntoIterator for &'a S2<T> {
26+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
27+
type IntoIter = std::slice::Iter<'a, T>;
28+
type Item = &'a T;
29+
fn into_iter(self) -> Self::IntoIter {
30+
todo!()
31+
}
32+
}
33+
impl<'a, T> IntoIterator for &'a mut S2<T> {
34+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
35+
type IntoIter = std::slice::IterMut<'a, T>;
36+
type Item = &'a mut T;
37+
fn into_iter(self) -> Self::IntoIter {
38+
todo!()
39+
}
40+
}
6641

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-
}
42+
// Both iter and iter_mut methods exist, don't lint
43+
pub struct S3<'a, T>(&'a T);
44+
impl<'a, T> S3<'a, T> {
45+
fn iter(&self) -> std::slice::Iter<'a, T> {
46+
todo!()
47+
}
48+
fn iter_mut(&mut self) -> std::slice::IterMut<'a, T> {
49+
todo!()
50+
}
51+
}
52+
impl<'a, T> IntoIterator for &S3<'a, T> {
53+
type IntoIter = std::slice::Iter<'a, T>;
54+
type Item = &'a T;
55+
fn into_iter(self) -> Self::IntoIter {
56+
todo!()
57+
}
58+
}
59+
impl<'a, T> IntoIterator for &mut S3<'a, T> {
60+
type IntoIter = std::slice::IterMut<'a, T>;
61+
type Item = &'a mut T;
62+
fn into_iter(self) -> Self::IntoIter {
63+
todo!()
7464
}
75-
{
76-
// Only `iter` exists, no `iter_mut`
77-
struct S<'a, T>(&'a T);
65+
}
7866

79-
impl<'a, T> S<'a, T> {
80-
fn iter(&self) -> std::slice::Iter<'a, T> {
81-
todo!()
82-
}
83-
}
67+
// Only `iter` exists, no `iter_mut`
68+
pub struct S4<'a, T>(&'a T);
8469

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-
}
70+
impl<'a, T> S4<'a, T> {
71+
fn iter(&self) -> std::slice::Iter<'a, T> {
72+
todo!()
73+
}
74+
}
9275

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-
}
76+
impl<'a, T> IntoIterator for &S4<'a, T> {
77+
type IntoIter = std::slice::Iter<'a, T>;
78+
type Item = &'a T;
79+
fn into_iter(self) -> Self::IntoIter {
80+
todo!()
10181
}
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;
82+
}
10783

108-
impl S {
109-
fn iter(&self) -> std::slice::Iter<'static, u8> {
110-
todo!()
111-
}
112-
}
84+
impl<'a, T> IntoIterator for &mut S4<'a, T> {
85+
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
86+
type IntoIter = std::slice::IterMut<'a, T>;
87+
type Item = &'a mut T;
88+
fn into_iter(self) -> Self::IntoIter {
89+
todo!()
90+
}
91+
}
11392

114-
type Alias = S;
93+
// `iter` exists, but `IntoIterator` is implemented for an alias. inherent_impls doesn't "normalize"
94+
// aliases so that `inherent_impls(Alias)` where `type Alias = S` returns nothing, so this can lead
95+
// to fun FPs. Make sure it doesn't happen here (we're using type_of, which should skip the alias).
96+
pub struct S5;
11597

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-
}
98+
impl S5 {
99+
fn iter(&self) -> std::slice::Iter<'static, u8> {
100+
todo!()
101+
}
102+
}
103+
104+
pub type Alias = S5;
105+
106+
impl IntoIterator for &Alias {
107+
type IntoIter = std::slice::Iter<'static, u8>;
108+
type Item = &'static u8;
109+
fn into_iter(self) -> Self::IntoIter {
110+
todo!()
123111
}
124112
}
125113

126-
fn issue11635() {
114+
fn main() {}
115+
116+
pub mod issue11635 {
127117
// A little more involved than the original repro in the issue, but this tests that it correctly
128118
// works for more than one deref step
129119

0 commit comments

Comments
 (0)