Skip to content

Commit 9875695

Browse files
useless_conversion: do not lint (a..b).into_iter() (for edition migration) (#16891)
In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b` will change from producing type `core::ops::Range`, which implements `Iterator`, to producing type `core::range::Range`, which implements `IntoIterator`. Therefore, an `.into_iter()` call that is technically useless today will be useful for edition migration or unstable feature testing; do not remove it. Also: * Fix issue number for existing tests * Fix docs reference to `IntoIter` (not the name of the trait) * Rename some variables to be clearer changelog: [`useless_conversion`]: do not lint on `(a..b).into_iter()`, to allow compatibility with future range syntax changes
2 parents a6c2c18 + 47bdbaa commit 9875695

4 files changed

Lines changed: 123 additions & 33 deletions

File tree

clippy_lints/src/useless_conversion.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use clippy_utils::ty::{is_copy, same_type_modulo_regions};
66
use clippy_utils::{get_parent_expr, is_ty_alias, sym};
77
use rustc_errors::Applicability;
88
use rustc_hir::def_id::DefId;
9-
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, PatKind};
9+
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, LangItem, MatchSource, Mutability, Node, PatKind};
1010
use rustc_infer::infer::TyCtxtInferExt;
1111
use rustc_infer::traits::Obligation;
1212
use rustc_lint::{LateContext, LateLintPass};
@@ -19,7 +19,7 @@ use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
1919

2020
declare_clippy_lint! {
2121
/// ### What it does
22-
/// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls
22+
/// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIterator` calls
2323
/// which uselessly convert to the same type.
2424
///
2525
/// ### Why is this bad?
@@ -38,7 +38,7 @@ declare_clippy_lint! {
3838
#[clippy::version = "1.45.0"]
3939
pub USELESS_CONVERSION,
4040
complexity,
41-
"calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type"
41+
"calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIterator` which perform useless conversions to the same type"
4242
}
4343

4444
impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]);
@@ -322,13 +322,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
322322
return;
323323
}
324324

325-
let a = cx.typeck_results().expr_ty(e);
326-
let b = cx.typeck_results().expr_ty(recv);
325+
let iter_ty = cx.typeck_results().expr_ty(e);
326+
let into_iter_ty = cx.typeck_results().expr_ty(recv);
327327

328328
// If the types are identical then .into_iter() can be removed, unless the type
329329
// implements Copy, in which case .into_iter() returns a copy of the receiver and
330330
// cannot be safely omitted.
331-
if same_type_modulo_regions(a, b) && !is_copy(cx, b) {
331+
if same_type_modulo_regions(iter_ty, into_iter_ty) && !is_copy(cx, into_iter_ty) {
332332
// Below we check if the parent method call meets the following conditions:
333333
// 1. First parameter is `&mut self` (requires mutable reference)
334334
// 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any)
@@ -356,6 +356,28 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
356356
return;
357357
}
358358

359+
// In a future edition of Rust (edition 2027, hopefully), or with the unstable
360+
// `feature(new_range)`, the syntax `a..b` will change from producing type `core::ops::Range`,
361+
// which implements `Iterator`, to producing type `core::range::Range`, which implements
362+
// `IntoIterator` only.
363+
//
364+
// Therefore, an `(a..b).into_iter()` call that is technically useless today will be useful for
365+
// edition migration or unstable feature testing; do not remove it.
366+
//
367+
// In the future, after most code has either migrated to the new range types or declined to
368+
// do so, this special case will be much less useful and could be removed.
369+
if let Some(parent) = get_parent_expr(cx, e)
370+
// Is a method call, not, say, a for loop where the conversion *is* useless.
371+
&& let ExprKind::MethodCall(_, _, _, _) = parent.kind
372+
// These lang items are the 3 core::ops range types that implement Iterator.
373+
// All other range types do not implement Iterator, so this lint does not apply to them.
374+
&& (into_iter_ty.is_lang_item(cx, LangItem::Range)
375+
|| into_iter_ty.is_lang_item(cx, LangItem::RangeFrom)
376+
|| into_iter_ty.is_lang_item(cx, LangItem::RangeInclusiveStruct))
377+
{
378+
return;
379+
}
380+
359381
let mut applicability = Applicability::MachineApplicable;
360382
let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "<expr>", &mut applicability)
361383
.0
@@ -364,7 +386,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
364386
cx,
365387
USELESS_CONVERSION,
366388
e.span,
367-
format!("useless conversion to the same type: `{b}`"),
389+
format!("useless conversion to the same type: `{into_iter_ty}`"),
368390
"consider removing `.into_iter()`",
369391
sugg,
370392
applicability,

tests/ui/useless_conversion.fixed

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ fn lint_into_iter_on_expr_implementing_iterator_2() {
7272

7373
#[allow(const_item_mutation)]
7474
fn lint_into_iter_on_const_implementing_iterator() {
75-
const NUMBERS: std::ops::Range<i32> = 0..10;
75+
const NUMBERS: std::iter::Empty<i32> = std::iter::empty();
7676
let _ = NUMBERS.next();
7777
//~^ useless_conversion
7878
}
7979

8080
fn lint_into_iter_on_const_implementing_iterator_2() {
81-
const NUMBERS: std::ops::Range<i32> = 0..10;
81+
const NUMBERS: std::iter::Empty<i32> = std::iter::empty();
8282
let mut n = NUMBERS;
8383
//~^ useless_conversion
8484
n.next();
@@ -423,10 +423,8 @@ mod issue11819 {
423423
}
424424
}
425425

426-
fn issue14739() {
427-
use std::ops::Range;
428-
429-
const R: Range<u32> = 2..7;
426+
fn issue14800() {
427+
const R: std::iter::Empty<u32> = std::iter::empty();
430428

431429
R.into_iter().all(|_x| true); // no lint
432430

@@ -438,6 +436,33 @@ fn issue14739() {
438436
//~^ useless_conversion
439437
}
440438

439+
// In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b`
440+
// will change from producing type `core::ops::Range`, which implements `Iterator`, to
441+
// producing type `core::range::Range`, which implements `IntoIterator`.
442+
//
443+
// Therefore, an `.into_iter()` call that is technically useless today will be useful for
444+
// edition migration or unstable feature testing; do not remove it.
445+
//
446+
// This test case tests that the ranges produced *by range syntax* aren’t linted on, which
447+
// should be true both before and after the expected 2027 edition migration (but after such
448+
// migration, this test will not really be testing anything).
449+
fn do_not_lint_on_ops_range_into_iter_before_method() {
450+
#![allow(clippy::never_loop)]
451+
452+
// No lint on these
453+
(0..10).into_iter().for_each(drop);
454+
(0..=10).into_iter().for_each(drop);
455+
(0..).into_iter().take(10).for_each(drop);
456+
457+
// But do still lint on for loops
458+
for _ in (0..10) {} //~ useless_conversion
459+
for _ in (0..=10) {} //~ useless_conversion
460+
for _ in (0..) {
461+
//~^ useless_conversion
462+
break;
463+
}
464+
}
465+
441466
fn issue16165() {
442467
macro_rules! mac {
443468
(iter $e:expr) => {

tests/ui/useless_conversion.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ fn lint_into_iter_on_expr_implementing_iterator_2() {
7272

7373
#[allow(const_item_mutation)]
7474
fn lint_into_iter_on_const_implementing_iterator() {
75-
const NUMBERS: std::ops::Range<i32> = 0..10;
75+
const NUMBERS: std::iter::Empty<i32> = std::iter::empty();
7676
let _ = NUMBERS.into_iter().next();
7777
//~^ useless_conversion
7878
}
7979

8080
fn lint_into_iter_on_const_implementing_iterator_2() {
81-
const NUMBERS: std::ops::Range<i32> = 0..10;
81+
const NUMBERS: std::iter::Empty<i32> = std::iter::empty();
8282
let mut n = NUMBERS.into_iter();
8383
//~^ useless_conversion
8484
n.next();
@@ -423,10 +423,8 @@ mod issue11819 {
423423
}
424424
}
425425

426-
fn issue14739() {
427-
use std::ops::Range;
428-
429-
const R: Range<u32> = 2..7;
426+
fn issue14800() {
427+
const R: std::iter::Empty<u32> = std::iter::empty();
430428

431429
R.into_iter().all(|_x| true); // no lint
432430

@@ -438,6 +436,33 @@ fn issue14739() {
438436
//~^ useless_conversion
439437
}
440438

439+
// In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b`
440+
// will change from producing type `core::ops::Range`, which implements `Iterator`, to
441+
// producing type `core::range::Range`, which implements `IntoIterator`.
442+
//
443+
// Therefore, an `.into_iter()` call that is technically useless today will be useful for
444+
// edition migration or unstable feature testing; do not remove it.
445+
//
446+
// This test case tests that the ranges produced *by range syntax* aren’t linted on, which
447+
// should be true both before and after the expected 2027 edition migration (but after such
448+
// migration, this test will not really be testing anything).
449+
fn do_not_lint_on_ops_range_into_iter_before_method() {
450+
#![allow(clippy::never_loop)]
451+
452+
// No lint on these
453+
(0..10).into_iter().for_each(drop);
454+
(0..=10).into_iter().for_each(drop);
455+
(0..).into_iter().take(10).for_each(drop);
456+
457+
// But do still lint on for loops
458+
for _ in (0..10).into_iter() {} //~ useless_conversion
459+
for _ in (0..=10).into_iter() {} //~ useless_conversion
460+
for _ in (0..).into_iter() {
461+
//~^ useless_conversion
462+
break;
463+
}
464+
}
465+
441466
fn issue16165() {
442467
macro_rules! mac {
443468
(iter $e:expr) => {

tests/ui/useless_conversion.stderr

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ error: useless conversion to the same type: `std::str::Lines<'_>`
4040
LL | if Some("ok") == text.lines().into_iter().next() {}
4141
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `text.lines()`
4242

43-
error: useless conversion to the same type: `std::ops::Range<i32>`
43+
error: useless conversion to the same type: `std::iter::Empty<i32>`
4444
--> tests/ui/useless_conversion.rs:76:13
4545
|
4646
LL | let _ = NUMBERS.into_iter().next();
4747
| ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `NUMBERS`
4848

49-
error: useless conversion to the same type: `std::ops::Range<i32>`
49+
error: useless conversion to the same type: `std::iter::Empty<i32>`
5050
--> tests/ui/useless_conversion.rs:82:17
5151
|
5252
LL | let mut n = NUMBERS.into_iter();
@@ -421,32 +421,50 @@ LL - takes_into_iter(self.my_field.into_iter());
421421
LL + takes_into_iter(&mut *self.my_field);
422422
|
423423

424-
error: useless conversion to the same type: `std::ops::Range<u32>`
425-
--> tests/ui/useless_conversion.rs:435:5
424+
error: useless conversion to the same type: `std::iter::Empty<u32>`
425+
--> tests/ui/useless_conversion.rs:433:5
426426
|
427427
LL | R.into_iter().for_each(|_x| {});
428428
| ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R`
429429

430-
error: useless conversion to the same type: `std::ops::Range<u32>`
431-
--> tests/ui/useless_conversion.rs:437:13
430+
error: useless conversion to the same type: `std::iter::Empty<u32>`
431+
--> tests/ui/useless_conversion.rs:435:13
432432
|
433433
LL | let _ = R.into_iter().map(|_x| 0);
434434
| ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R`
435435

436+
error: useless conversion to the same type: `std::ops::Range<i32>`
437+
--> tests/ui/useless_conversion.rs:458:14
438+
|
439+
LL | for _ in (0..10).into_iter() {}
440+
| ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..10)`
441+
442+
error: useless conversion to the same type: `std::ops::RangeInclusive<i32>`
443+
--> tests/ui/useless_conversion.rs:459:14
444+
|
445+
LL | for _ in (0..=10).into_iter() {}
446+
| ^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..=10)`
447+
448+
error: useless conversion to the same type: `std::ops::RangeFrom<i32>`
449+
--> tests/ui/useless_conversion.rs:460:14
450+
|
451+
LL | for _ in (0..).into_iter() {
452+
| ^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..)`
453+
436454
error: useless conversion to the same type: `std::slice::Iter<'_, i32>`
437-
--> tests/ui/useless_conversion.rs:448:14
455+
--> tests/ui/useless_conversion.rs:473:14
438456
|
439457
LL | for _ in mac!(iter [1, 2]).into_iter() {}
440458
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `mac!(iter [1, 2])`
441459

442460
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
443-
--> tests/ui/useless_conversion.rs:461:27
461+
--> tests/ui/useless_conversion.rs:486:27
444462
|
445463
LL | takes_into_iter_usize(b.into_iter());
446464
| ^^^^^^^^^^^^^
447465
|
448466
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
449-
--> tests/ui/useless_conversion.rs:452:34
467+
--> tests/ui/useless_conversion.rs:477:34
450468
|
451469
LL | fn takes_into_iter_usize(_: impl IntoIterator<Item = usize>) {}
452470
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -457,13 +475,13 @@ LL + takes_into_iter_usize(b);
457475
|
458476

459477
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
460-
--> tests/ui/useless_conversion.rs:470:31
478+
--> tests/ui/useless_conversion.rs:495:31
461479
|
462480
LL | takes_into_iter_usize(b.clone().into_iter());
463481
| ^^^^^^^^^^^^^^^^^^^^^
464482
|
465483
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
466-
--> tests/ui/useless_conversion.rs:452:34
484+
--> tests/ui/useless_conversion.rs:477:34
467485
|
468486
LL | fn takes_into_iter_usize(_: impl IntoIterator<Item = usize>) {}
469487
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -474,13 +492,13 @@ LL + takes_into_iter_usize(b.clone());
474492
|
475493

476494
error: explicit call to `.into_iter()` in function argument accepting `IntoIterator`
477-
--> tests/ui/useless_conversion.rs:478:34
495+
--> tests/ui/useless_conversion.rs:503:34
478496
|
479497
LL | takes_into_iter_usize_result(b.clone().into_iter())?;
480498
| ^^^^^^^^^^^^^^^^^^^^^
481499
|
482500
note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`
483-
--> tests/ui/useless_conversion.rs:453:41
501+
--> tests/ui/useless_conversion.rs:478:41
484502
|
485503
LL | fn takes_into_iter_usize_result(_: impl IntoIterator<Item = usize>) -> Result<(), ()> {
486504
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -490,5 +508,5 @@ LL - takes_into_iter_usize_result(b.clone().into_iter())?;
490508
LL + takes_into_iter_usize_result(b.clone())?;
491509
|
492510

493-
error: aborting due to 48 previous errors
511+
error: aborting due to 51 previous errors
494512

0 commit comments

Comments
 (0)