Skip to content

Commit 68b1e8b

Browse files
committed
new lint: iter_without_into_iter
1 parent 251a475 commit 68b1e8b

File tree

6 files changed

+397
-0
lines changed

6 files changed

+397
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
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

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

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ mod invalid_upcast_comparisons;
167167
mod items_after_statements;
168168
mod items_after_test_module;
169169
mod iter_not_returning_iterator;
170+
mod iter_without_into_iter;
170171
mod large_const_arrays;
171172
mod large_enum_variant;
172173
mod large_futures;
@@ -1117,6 +1118,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11171118
msrv(),
11181119
))
11191120
});
1121+
store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter));
11201122
// add lints here, do not remove this comment, it's used in `new_lint`
11211123
}
11221124

tests/ui/iter_without_into_iter.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
}
67+
{
68+
struct S<T>(T);
69+
impl<T> S<T> {
70+
pub fn iter(&self) -> std::slice::Iter<'_, T> {
71+
//~^ ERROR: `iter` method without an `IntoIterator` impl
72+
todo!()
73+
}
74+
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
75+
//~^ ERROR: `iter_mut` method without an `IntoIterator` impl
76+
todo!()
77+
}
78+
}
79+
}
80+
{
81+
struct S<T>(T);
82+
impl<T> S<T> {
83+
pub fn iter(&self) -> std::slice::Iter<'_, T> {
84+
// Don't lint, there's an existing (wrong) IntoIterator impl
85+
todo!()
86+
}
87+
}
88+
89+
impl<'a, T> IntoIterator for &'a S<T> {
90+
type Item = &'a String;
91+
type IntoIter = std::slice::Iter<'a, String>;
92+
fn into_iter(self) -> Self::IntoIter {
93+
todo!()
94+
}
95+
}
96+
}
97+
{
98+
struct S<T>(T);
99+
impl<T> S<T> {
100+
pub fn iter_mut(&self) -> std::slice::IterMut<'_, T> {
101+
// Don't lint, there's an existing (wrong) IntoIterator impl
102+
todo!()
103+
}
104+
}
105+
106+
impl<'a, T> IntoIterator for &'a mut S<T> {
107+
type Item = &'a mut String;
108+
type IntoIter = std::slice::IterMut<'a, String>;
109+
fn into_iter(self) -> Self::IntoIter {
110+
todo!()
111+
}
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)