Skip to content

Commit 7624045

Browse files
committed
Auto merge of #11639 - y21:issue11635, r=llogiq
[`into_iter_without_iter`]: walk up deref impl chain to find `iter` methods Fixes #11635 changelog: [`into_iter_without_iter`]: walk up deref impl chain to find `iter` methods
2 parents 3662bd8 + 1c6fa29 commit 7624045

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

clippy_lints/src/iter_without_into_iter.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_lint::{LateContext, LateLintPass};
99
use rustc_middle::ty::{self, Ty};
1010
use rustc_session::{declare_lint_pass, declare_tool_lint};
1111
use rustc_span::{sym, Symbol};
12+
use std::iter;
1213

1314
declare_clippy_lint! {
1415
/// ### What it does
@@ -52,7 +53,8 @@ declare_clippy_lint! {
5253
declare_clippy_lint! {
5354
/// ### What it does
5455
/// 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+
/// It looks for `IntoIterator for (&|&mut) Type` implementations without an inherent `iter` or `iter_mut` method
57+
/// on the type or on any of the types in its `Deref` chain.
5658
///
5759
/// ### Why is this bad?
5860
/// It's not bad, but having them is idiomatic and allows the type to be used in iterator chains
@@ -102,7 +104,20 @@ fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
102104
!matches!(ty.kind, TyKind::OpaqueDef(..))
103105
}
104106

105-
fn type_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
107+
/// Returns the deref chain of a type, starting with the type itself.
108+
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
109+
iter::successors(Some(ty), |&ty| {
110+
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
111+
&& implements_trait(cx, ty, deref_did, &[])
112+
{
113+
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
114+
} else {
115+
None
116+
}
117+
})
118+
}
119+
120+
fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
106121
if let Some(ty_did) = ty.ty_adt_def().map(ty::AdtDef::did) {
107122
cx.tcx.inherent_impls(ty_did).iter().any(|&did| {
108123
cx.tcx
@@ -127,7 +142,11 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
127142
Mutability::Mut => sym::iter_mut,
128143
Mutability::Not => sym::iter,
129144
}
130-
&& !type_has_inherent_method(cx, ty, expected_method_name)
145+
&& !deref_chain(cx, ty)
146+
.any(|ty| {
147+
// We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method
148+
ty.peel_refs().is_slice() || adt_has_inherent_method(cx, ty, expected_method_name)
149+
})
131150
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
132151
if item.ident.name == sym!(IntoIter) {
133152
Some(cx.tcx.hir().impl_item(item.id).expect_type().span)

tests/ui/into_iter_without_iter.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,37 @@ fn main() {
122122
}
123123
}
124124
}
125+
126+
fn issue11635() {
127+
// A little more involved than the original repro in the issue, but this tests that it correctly
128+
// works for more than one deref step
129+
130+
use std::ops::Deref;
131+
132+
pub struct Thing(Vec<u8>);
133+
pub struct Thing2(Thing);
134+
135+
impl Deref for Thing {
136+
type Target = [u8];
137+
138+
fn deref(&self) -> &Self::Target {
139+
&self.0
140+
}
141+
}
142+
143+
impl Deref for Thing2 {
144+
type Target = Thing;
145+
fn deref(&self) -> &Self::Target {
146+
&self.0
147+
}
148+
}
149+
150+
impl<'a> IntoIterator for &'a Thing2 {
151+
type Item = &'a u8;
152+
type IntoIter = <&'a [u8] as IntoIterator>::IntoIter;
153+
154+
fn into_iter(self) -> Self::IntoIter {
155+
self.0.iter()
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)