fix: redundant_clone FP in overlapping lifetime (#14237)

fixes #13900

changelog: [`redundant_clone`]: fix FP in overlapping lifetime
This commit is contained in:
Samuel Tardieu 2025-04-11 15:47:20 +00:00 committed by GitHub
commit ec105bab2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 70 additions and 8 deletions

View file

@ -182,15 +182,20 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
let clone_usage = if local == ret_local {
CloneUsage {
cloned_used: false,
cloned_use_loc: None.into(),
cloned_consume_or_mutate_loc: None,
clone_consumed_or_mutated: true,
}
} else {
let clone_usage = visit_clone_usage(local, ret_local, mir, bb);
if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
if clone_usage.cloned_use_loc.maybe_used() && clone_usage.clone_consumed_or_mutated {
// cloned value is used, and the clone is modified or moved
continue;
} else if let MirLocalUsage::Used(loc) = clone_usage.cloned_use_loc
&& possible_borrower.local_is_alive_at(ret_local, loc)
{
// cloned value is used, and the clone is alive.
continue;
} else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc
// cloned value is mutated, and the clone is alive.
&& possible_borrower.local_is_alive_at(ret_local, loc)
@ -227,7 +232,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
diag.span_suggestion(sugg_span, "remove this", "", app);
if clone_usage.cloned_used {
if clone_usage.cloned_use_loc.maybe_used() {
diag.span_note(span, "cloned value is neither consumed nor mutated");
} else {
diag.span_note(
@ -328,10 +333,33 @@ fn base_local_and_movability<'tcx>(
(place.local, deref || field || slice)
}
#[derive(Default)]
#[derive(Debug, Default)]
enum MirLocalUsage {
/// The local maybe used, but we are not sure how.
Unknown,
/// The local is not used.
#[default]
Unused,
/// The local is used at a specific location.
Used(mir::Location),
}
impl MirLocalUsage {
fn maybe_used(&self) -> bool {
matches!(self, MirLocalUsage::Unknown | MirLocalUsage::Used(_))
}
}
impl From<Option<mir::Location>> for MirLocalUsage {
fn from(loc: Option<mir::Location>) -> Self {
loc.map_or(MirLocalUsage::Unused, MirLocalUsage::Used)
}
}
#[derive(Debug, Default)]
struct CloneUsage {
/// Whether the cloned value is used after the clone.
cloned_used: bool,
/// The first location where the cloned value is used, if any.
cloned_use_loc: MirLocalUsage,
/// The first location where the cloned value is consumed or mutated, if any.
cloned_consume_or_mutate_loc: Option<mir::Location>,
/// Whether the clone value is mutated.
@ -359,7 +387,7 @@ fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>,
.map(|mut vec| (vec.remove(0), vec.remove(0)))
{
CloneUsage {
cloned_used: !cloned_use_locs.is_empty(),
cloned_use_loc: cloned_use_locs.first().copied().into(),
cloned_consume_or_mutate_loc: cloned_consume_or_mutate_locs.first().copied(),
// Consider non-temporary clones consumed.
// TODO: Actually check for mutation of non-temporaries.
@ -368,7 +396,7 @@ fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>,
}
} else {
CloneUsage {
cloned_used: true,
cloned_use_loc: MirLocalUsage::Unknown,
cloned_consume_or_mutate_loc: None,
clone_consumed_or_mutated: true,
}

View file

@ -274,3 +274,20 @@ mod issue10074 {
println!("{v}");
}
}
mod issue13900 {
use std::fmt::Display;
fn do_something(f: impl Display + Clone) -> String {
let g = f.clone();
format!("{} + {}", f, g)
}
fn regression() {
let mut a = String::new();
let mut b = String::new();
for _ in 1..10 {
b = a.clone();
}
}
}

View file

@ -274,3 +274,20 @@ mod issue10074 {
println!("{v}");
}
}
mod issue13900 {
use std::fmt::Display;
fn do_something(f: impl Display + Clone) -> String {
let g = f.clone();
format!("{} + {}", f, g)
}
fn regression() {
let mut a = String::new();
let mut b = String::new();
for _ in 1..10 {
b = a.clone();
}
}
}