Skip to content

Commit e092c60

Browse files
Fix regression in useless_conversion lint on non-const items
1 parent b2f88e9 commit e092c60

4 files changed

Lines changed: 71 additions & 5 deletions

File tree

clippy_lints/src/useless_conversion.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clippy_utils::sugg::{DiagExt as _, Sugg};
55
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;
8+
use rustc_hir::def::{DefKind, Res};
89
use rustc_hir::def_id::DefId;
910
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, PatKind};
1011
use rustc_infer::infer::TyCtxtInferExt;
@@ -329,10 +330,17 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
329330
// implements Copy, in which case .into_iter() returns a copy of the receiver and
330331
// cannot be safely omitted.
331332
if same_type_modulo_regions(a, b) && !is_copy(cx, b) {
332-
// Below we check if the parent method call meets the following conditions:
333-
// 1. First parameter is `&mut self` (requires mutable reference)
334-
// 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any)
335-
// For methods satisfying these conditions (like any), .into_iter() must be preserved.
333+
// Suppress the lint when the direct receiver of `.into_iter()` is a `const`
334+
// (or `AssocConst`) item AND the parent method takes `&mut self` + `FnMut`.
335+
//
336+
// Background: removing `.into_iter()` from `CONST.method(|x| ...)` where
337+
// `method` takes `&mut self` causes the compiler to take a mutable reference
338+
// to a *temporary copy* of the const, triggering a `const_item_mutation`
339+
// warning. For any other receiver expression this is safe to remove.
340+
//
341+
// See <https://github.com/rust-lang/rust-clippy/issues/14656> for the
342+
// original false positive and <https://github.com/rust-lang/rust-clippy/issues/16794>
343+
// for the regression caused by applying this guard too broadly.
336344
if let Some(parent) = get_parent_expr(cx, e)
337345
&& let ExprKind::MethodCall(_, recv, _, _) = parent.kind
338346
&& recv.hir_id == e.hir_id
@@ -352,6 +360,9 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
352360
false
353361
}
354362
})
363+
// Only suppress when the `.into_iter()` receiver is a const item.
364+
// For any other expression the removal is safe.
365+
&& is_const_item(cx, into_iter_recv)
355366
{
356367
return;
357368
}
@@ -480,3 +491,20 @@ fn adjustments(cx: &LateContext<'_>, expr: &Expr<'_>) -> String {
480491
}
481492
prefix
482493
}
494+
495+
/// Returns `true` when `expr` is a direct reference to a `const` or `AssocConst` item.
496+
///
497+
/// This is used to guard the `.into_iter()` suppression: only const items cause
498+
/// `const_item_mutation` warnings when a `&mut self` method is called directly on
499+
/// them (the compiler takes a mutable reference to a *temporary copy*). For any other
500+
/// expression it is safe to remove the `.into_iter()` call.
501+
fn is_const_item(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
502+
if let ExprKind::Path(qpath) = &expr.kind {
503+
matches!(
504+
cx.qpath_res(qpath, expr.hir_id),
505+
Res::Def(DefKind::Const { .. } | DefKind::AssocConst { .. }, _)
506+
)
507+
} else {
508+
false
509+
}
510+
}

tests/ui/useless_conversion.fixed

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,18 @@ fn after_question_mark() -> Result<(), ()> {
479479
//~^ useless_conversion
480480
Ok(())
481481
}
482+
483+
fn issue16794() {
484+
// Non-const receiver: removing .into_iter() is safe, should lint.
485+
// `s.chars()` returns a `Chars<'_>` which is already an Iterator.
486+
let s = "hello";
487+
s.chars()
488+
//~^^ useless_conversion
489+
.any(|c| c == 'a');
490+
491+
// Const Range with .any(): still no lint (original false-positive from #14656 must stay fixed).
492+
use std::ops::Range;
493+
const R: Range<u32> = 2..7;
494+
R.into_iter().any(|_x| true); // no lint
495+
R.into_iter().all(|_x| true); // no lint
496+
}

tests/ui/useless_conversion.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,19 @@ fn after_question_mark() -> Result<(), ()> {
479479
//~^ useless_conversion
480480
Ok(())
481481
}
482+
483+
fn issue16794() {
484+
// Non-const receiver: removing .into_iter() is safe, should lint.
485+
// `s.chars()` returns a `Chars<'_>` which is already an Iterator.
486+
let s = "hello";
487+
s.chars()
488+
.into_iter()
489+
//~^^ useless_conversion
490+
.any(|c| c == 'a');
491+
492+
// Const Range with .any(): still no lint (original false-positive from #14656 must stay fixed).
493+
use std::ops::Range;
494+
const R: Range<u32> = 2..7;
495+
R.into_iter().any(|_x| true); // no lint
496+
R.into_iter().all(|_x| true); // no lint
497+
}

tests/ui/useless_conversion.stderr

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,5 +490,12 @@ LL - takes_into_iter_usize_result(b.clone().into_iter())?;
490490
LL + takes_into_iter_usize_result(b.clone())?;
491491
|
492492

493-
error: aborting due to 48 previous errors
493+
error: useless conversion to the same type: `std::str::Chars<'_>`
494+
--> tests/ui/useless_conversion.rs:487:5
495+
|
496+
LL | / s.chars()
497+
LL | | .into_iter()
498+
| |____________________^ help: consider removing `.into_iter()`: `s.chars()`
499+
500+
error: aborting due to 49 previous errors
494501

0 commit comments

Comments
 (0)