Skip to content

Commit

Permalink
Auto merge of #11696 - y21:iter_without_into_iter_suggestion, r=xFrednet
Browse files Browse the repository at this point in the history
[`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 #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)
  • Loading branch information
bors committed Oct 28, 2023
2 parents 2f0f4dd + 9a10d32 commit f8409ef
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 343 deletions.
25 changes: 23 additions & 2 deletions clippy_lints/src/iter_without_into_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ declare_clippy_lint! {
/// It's not bad, but having them is idiomatic and allows the type to be used in for loops directly
/// (`for val in &iter {}`), without having to first call `iter()` or `iter_mut()`.
///
/// ### Limitations
/// This lint focuses on providing an idiomatic API. Therefore, it will only
/// lint on types which are accessible outside of the crate. For internal types,
/// the `IntoIterator` trait can be implemented on demand if it is actually needed.
///
/// ### Example
/// ```no_run
/// struct MySlice<'a>(&'a [u8]);
Expand Down Expand Up @@ -61,6 +66,14 @@ declare_clippy_lint! {
/// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).into_iter()` syntax
/// in case of ambiguity with another `IntoIterator` impl.
///
/// ### Limitations
/// This lint focuses on providing an idiomatic API. Therefore, it will only
/// lint on types which are accessible outside of the crate. For internal types,
/// these methods can be added on demand if they are actually needed. Otherwise,
/// it would trigger the [`dead_code`] lint for the unused method.
///
/// [`dead_code`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#dead-code
///
/// ### Example
/// ```no_run
/// struct MySlice<'a>(&'a [u8]);
Expand Down Expand Up @@ -104,6 +117,12 @@ fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
!matches!(ty.kind, TyKind::OpaqueDef(..))
}

fn is_ty_exported(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
ty.ty_adt_def()
.and_then(|adt| adt.did().as_local())
.is_some_and(|did| cx.effective_visibilities.is_exported(did))
}

/// Returns the deref chain of a type, starting with the type itself.
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
iter::successors(Some(ty), |&ty| {
Expand Down Expand Up @@ -154,6 +173,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
None
}
})
&& is_ty_exported(cx, ty)
{
span_lint_and_then(
cx,
Expand Down Expand Up @@ -226,6 +246,7 @@ impl {self_ty_without_ref} {{
)
// Only lint if the `IntoIterator` impl doesn't actually exist
&& !implements_trait(cx, ref_ty, into_iter_did, &[])
&& is_ty_exported(cx, ref_ty.peel_refs())
{
let self_ty_snippet = format!("{borrow_prefix}{}", snippet(cx, imp.self_ty.span, ".."));

Expand All @@ -247,8 +268,8 @@ impl {self_ty_without_ref} {{
"
impl IntoIterator for {self_ty_snippet} {{
type IntoIter = {ret_ty};
type Iter = {iter_ty};
fn into_iter() -> Self::IntoIter {{
type Item = {iter_ty};
fn into_iter(self) -> Self::IntoIter {{
self.iter()
}}
}}
Expand Down
202 changes: 96 additions & 106 deletions tests/ui/into_iter_without_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,127 +3,117 @@

use std::iter::IntoIterator;

fn main() {
{
struct S;

impl<'a> IntoIterator for &'a S {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
type IntoIter = std::slice::Iter<'a, u8>;
type Item = &'a u8;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl<'a> IntoIterator for &'a mut S {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
type IntoIter = std::slice::IterMut<'a, u8>;
type Item = &'a mut u8;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
pub struct S1;
impl<'a> IntoIterator for &'a S1 {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
type IntoIter = std::slice::Iter<'a, u8>;
type Item = &'a u8;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
{
struct S<T>(T);
impl<'a, T> IntoIterator for &'a S<T> {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl<'a, T> IntoIterator for &'a mut S<T> {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
}
impl<'a> IntoIterator for &'a mut S1 {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
type IntoIter = std::slice::IterMut<'a, u8>;
type Item = &'a mut u8;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
{
// Both iter and iter_mut methods exist, don't lint
struct S<'a, T>(&'a T);

impl<'a, T> S<'a, T> {
fn iter(&self) -> std::slice::Iter<'a, T> {
todo!()
}
fn iter_mut(&mut self) -> std::slice::IterMut<'a, T> {
todo!()
}
}
}

impl<'a, T> IntoIterator for &S<'a, T> {
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
pub struct S2<T>(T);
impl<'a, T> IntoIterator for &'a S2<T> {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter` method
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl<'a, T> IntoIterator for &'a mut S2<T> {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}

impl<'a, T> IntoIterator for &mut S<'a, T> {
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
// Both iter and iter_mut methods exist, don't lint
pub struct S3<'a, T>(&'a T);
impl<'a, T> S3<'a, T> {
fn iter(&self) -> std::slice::Iter<'a, T> {
todo!()
}
fn iter_mut(&mut self) -> std::slice::IterMut<'a, T> {
todo!()
}
}
impl<'a, T> IntoIterator for &S3<'a, T> {
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl<'a, T> IntoIterator for &mut S3<'a, T> {
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
{
// Only `iter` exists, no `iter_mut`
struct S<'a, T>(&'a T);
}

impl<'a, T> S<'a, T> {
fn iter(&self) -> std::slice::Iter<'a, T> {
todo!()
}
}
// Only `iter` exists, no `iter_mut`
pub struct S4<'a, T>(&'a T);

impl<'a, T> IntoIterator for &S<'a, T> {
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl<'a, T> S4<'a, T> {
fn iter(&self) -> std::slice::Iter<'a, T> {
todo!()
}
}

impl<'a, T> IntoIterator for &mut S<'a, T> {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl<'a, T> IntoIterator for &S4<'a, T> {
type IntoIter = std::slice::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
{
// `iter` exists, but `IntoIterator` is implemented for an alias. inherent_impls doesn't "normalize"
// aliases so that `inherent_impls(Alias)` where `type Alias = S` returns nothing, so this can lead
// to fun FPs. Make sure it doesn't happen here (we're using type_of, which should skip the alias).
struct S;
}

impl S {
fn iter(&self) -> std::slice::Iter<'static, u8> {
todo!()
}
}
impl<'a, T> IntoIterator for &mut S4<'a, T> {
//~^ ERROR: `IntoIterator` implemented for a reference type without an `iter_mut` method
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}

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

impl IntoIterator for &Alias {
type IntoIter = std::slice::Iter<'static, u8>;
type Item = &'static u8;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}
impl S5 {
fn iter(&self) -> std::slice::Iter<'static, u8> {
todo!()
}
}

pub type Alias = S5;

impl IntoIterator for &Alias {
type IntoIter = std::slice::Iter<'static, u8>;
type Item = &'static u8;
fn into_iter(self) -> Self::IntoIter {
todo!()
}
}

fn issue11635() {
fn main() {}

pub mod issue11635 {
// A little more involved than the original repro in the issue, but this tests that it correctly
// works for more than one deref step

Expand Down
Loading

0 comments on commit f8409ef

Please sign in to comment.