Skip to content

Commit 330ebbb

Browse files
committed
new lint: iter_without_into_iter
1 parent 91997a4 commit 330ebbb

6 files changed

+409
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5036,6 +5036,7 @@ Released 2018-09-13
50365036
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
50375037
[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero
50385038
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
5039+
[`iter_without_into_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_without_into_iter
50395040
[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
50405041
[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
50415042
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays

clippy_lints/src/declared_lints.rs

+1
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::ITER_WITHOUT_INTO_ITER_INFO,
232233
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
233234
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
234235
crate::large_futures::LARGE_FUTURES_INFO,
+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::get_parent_as_impl;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::ty::{implements_trait, make_normalized_projection};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, TyKind};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
use rustc_span::sym;
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Looks for `iter` and `iter_mut` methods without an associated `IntoIterator for (&|&mut) Type` implementation.
14+
///
15+
/// ### Why is this bad?
16+
/// It's not bad, but having them is idiomatic and allows the type to be used in for loops directly
17+
/// (`for val in &iter {}`), without having to first call `iter()` or `iter_mut()`.
18+
///
19+
/// ### Example
20+
/// ```rust
21+
/// struct MySlice<'a>(&'a [u8]);
22+
/// impl<'a> MySlice<'a> {
23+
/// pub fn iter(&self) -> std::slice::Iter<'a, u8> {
24+
/// self.0.iter()
25+
/// }
26+
/// }
27+
/// ```
28+
/// Use instead:
29+
/// ```rust
30+
/// struct MySlice<'a>(&'a [u8]);
31+
/// impl<'a> MySlice<'a> {
32+
/// pub fn iter(&self) -> std::slice::Iter<'a, u8> {
33+
/// self.0.iter()
34+
/// }
35+
/// }
36+
/// impl<'a> IntoIterator for &MySlice<'a> {
37+
/// type Item = &'a u8;
38+
/// type IntoIter = std::slice::Iter<'a, u8>;
39+
/// fn into_iter(self) -> Self::IntoIter {
40+
/// self.iter()
41+
/// }
42+
/// }
43+
/// ```
44+
#[clippy::version = "1.74.0"]
45+
pub ITER_WITHOUT_INTO_ITER,
46+
pedantic,
47+
"implementing `iter(_mut)` without an associated `IntoIterator for (&|&mut) Type` impl"
48+
}
49+
declare_lint_pass!(IterWithoutIntoIter => [ITER_WITHOUT_INTO_ITER]);
50+
51+
/// Checks if a given type is nameable in a trait (impl).
52+
/// RPIT is stable, but impl Trait in traits is not (yet), so when we have
53+
/// a function such as `fn iter(&self) -> impl IntoIterator`, we can't
54+
/// suggest `type IntoIter = impl IntoIterator`.
55+
fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
56+
!matches!(ty.kind, TyKind::OpaqueDef(..))
57+
}
58+
59+
impl LateLintPass<'_> for IterWithoutIntoIter {
60+
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) {
61+
let item_did = item.owner_id.to_def_id();
62+
let (borrow_prefix, expected_implicit_self) = match item.ident.name {
63+
sym::iter => ("&", ImplicitSelfKind::ImmRef),
64+
sym::iter_mut => ("&mut ", ImplicitSelfKind::MutRef),
65+
_ => return,
66+
};
67+
68+
if let ImplItemKind::Fn(sig, _) = item.kind
69+
&& let FnRetTy::Return(ret) = sig.decl.output
70+
&& is_nameable_in_impl_trait(ret)
71+
&& cx.tcx.generics_of(item_did).params.is_empty()
72+
&& sig.decl.implicit_self == expected_implicit_self
73+
&& sig.decl.inputs.len() == 1
74+
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
75+
&& imp.of_trait.is_none()
76+
&& let sig = cx.tcx.liberate_late_bound_regions(
77+
item_did,
78+
cx.tcx.fn_sig(item_did).instantiate_identity()
79+
)
80+
&& let ref_ty = sig.inputs()[0]
81+
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
82+
&& let Some(iterator_did) = cx.tcx.get_diagnostic_item(sym::Iterator)
83+
&& let ret_ty = sig.output()
84+
// Order is important here, we need to check that the `fn iter` return type actually implements `IntoIterator`
85+
// *before* normalizing `<_ as IntoIterator>::Item` (otherwise make_normalized_projection ICEs)
86+
&& implements_trait(cx, ret_ty, iterator_did, &[])
87+
&& let Some(iter_ty) = make_normalized_projection(
88+
cx.tcx,
89+
cx.param_env,
90+
iterator_did,
91+
sym!(Item),
92+
[ret_ty],
93+
)
94+
// Only lint if the `IntoIterator` impl doesn't actually exist
95+
&& !implements_trait(cx, ref_ty, into_iter_did, &[])
96+
{
97+
let self_ty_snippet = format!("{borrow_prefix}{}", snippet(cx, imp.self_ty.span, ".."));
98+
99+
span_lint_and_then(
100+
cx,
101+
ITER_WITHOUT_INTO_ITER,
102+
item.span,
103+
&format!("`{}` method without an `IntoIterator` impl for `{self_ty_snippet}`", item.ident),
104+
|diag| {
105+
// Get the lower span of the `impl` block, and insert the suggestion right before it:
106+
// impl X {
107+
// ^ fn iter(&self) -> impl IntoIterator { ... }
108+
// }
109+
let span_behind_impl = cx.tcx
110+
.def_span(cx.tcx.hir().parent_id(item.hir_id()).owner.def_id)
111+
.shrink_to_lo();
112+
113+
let sugg = format!(
114+
"
115+
impl IntoIterator for {self_ty_snippet} {{
116+
type IntoIter = {ret_ty};
117+
type Iter = {iter_ty};
118+
fn into_iter() -> Self::IntoIter {{
119+
self.iter()
120+
}}
121+
}}
122+
"
123+
);
124+
diag.span_suggestion_verbose(
125+
span_behind_impl,
126+
format!("consider implementing `IntoIterator` for `{self_ty_snippet}`"),
127+
sugg,
128+
// Suggestion is on a best effort basis, might need some adjustments by the user
129+
// such as adding some lifetimes in the associated types, or importing types.
130+
Applicability::Unspecified,
131+
);
132+
});
133+
}
134+
}
135+
}

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ mod invalid_upcast_comparisons;
169169
mod items_after_statements;
170170
mod items_after_test_module;
171171
mod iter_not_returning_iterator;
172+
mod iter_without_into_iter;
172173
mod large_const_arrays;
173174
mod large_enum_variant;
174175
mod large_futures;
@@ -1121,6 +1122,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11211122
))
11221123
});
11231124
store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
1125+
store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter));
11241126
// add lints here, do not remove this comment, it's used in `new_lint`
11251127
}
11261128

tests/ui/iter_without_into_iter.rs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//@no-rustfix
2+
#![warn(clippy::iter_without_into_iter)]
3+
4+
fn main() {
5+
{
6+
struct S;
7+
impl S {
8+
pub fn iter(&self) -> std::slice::Iter<'_, u8> {
9+
//~^ ERROR: `iter` method without an `IntoIterator` impl
10+
[].iter()
11+
}
12+
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, u8> {
13+
//~^ ERROR: `iter_mut` method without an `IntoIterator` impl
14+
[].iter_mut()
15+
}
16+
}
17+
}
18+
{
19+
struct S;
20+
impl S {
21+
pub fn iter(&self) -> impl Iterator<Item = &u8> {
22+
// RPITIT is not stable, so we can't generally suggest it here yet
23+
[].iter()
24+
}
25+
}
26+
}
27+
{
28+
struct S<'a>(&'a mut [u8]);
29+
impl<'a> S<'a> {
30+
pub fn iter(&self) -> std::slice::Iter<'_, u8> {
31+
//~^ ERROR: `iter` method without an `IntoIterator` impl
32+
self.0.iter()
33+
}
34+
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, u8> {
35+
//~^ ERROR: `iter_mut` method without an `IntoIterator` impl
36+
self.0.iter_mut()
37+
}
38+
}
39+
}
40+
{
41+
// Incompatible signatures
42+
struct S;
43+
impl S {
44+
pub fn iter(self) -> std::slice::Iter<'static, u8> {
45+
todo!()
46+
}
47+
}
48+
struct S2;
49+
impl S2 {
50+
pub async fn iter(&self) -> std::slice::Iter<'static, u8> {
51+
todo!()
52+
}
53+
}
54+
struct S3;
55+
impl S3 {
56+
pub fn iter(&self, _additional_param: ()) -> std::slice::Iter<'static, u8> {
57+
todo!()
58+
}
59+
}
60+
struct S4<T>(T);
61+
impl<T> S4<T> {
62+
pub fn iter<U>(&self) -> std::slice::Iter<'static, (T, U)> {
63+
todo!()
64+
}
65+
}
66+
struct S5<T>(T);
67+
impl<T> S5<T> {
68+
pub fn iter(&self) -> std::slice::Iter<'static, T> {
69+
todo!()
70+
}
71+
}
72+
}
73+
{
74+
struct S<T>(T);
75+
impl<T> S<T> {
76+
pub fn iter(&self) -> std::slice::Iter<'_, T> {
77+
//~^ ERROR: `iter` method without an `IntoIterator` impl
78+
todo!()
79+
}
80+
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
81+
//~^ ERROR: `iter_mut` method without an `IntoIterator` impl
82+
todo!()
83+
}
84+
}
85+
}
86+
{
87+
struct S<T>(T);
88+
impl<T> S<T> {
89+
pub fn iter(&self) -> std::slice::Iter<'_, T> {
90+
// Don't lint, there's an existing (wrong) IntoIterator impl
91+
todo!()
92+
}
93+
}
94+
95+
impl<'a, T> IntoIterator for &'a S<T> {
96+
type Item = &'a String;
97+
type IntoIter = std::slice::Iter<'a, String>;
98+
fn into_iter(self) -> Self::IntoIter {
99+
todo!()
100+
}
101+
}
102+
}
103+
{
104+
struct S<T>(T);
105+
impl<T> S<T> {
106+
pub fn iter_mut(&self) -> std::slice::IterMut<'_, T> {
107+
// Don't lint, there's an existing (wrong) IntoIterator impl
108+
todo!()
109+
}
110+
}
111+
112+
impl<'a, T> IntoIterator for &'a mut S<T> {
113+
type Item = &'a mut String;
114+
type IntoIter = std::slice::IterMut<'a, String>;
115+
fn into_iter(self) -> Self::IntoIter {
116+
todo!()
117+
}
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)