diff --git a/clippy_lints/src/replace_box.rs b/clippy_lints/src/replace_box.rs index e49a766acc26..3674a0000679 100644 --- a/clippy_lints/src/replace_box.rs +++ b/clippy_lints/src/replace_box.rs @@ -12,7 +12,7 @@ use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, Pl use rustc_lint::{LateContext, LateLintPass, impl_lint_pass}; use rustc_middle::hir::place::ProjectionKind; use rustc_middle::mir::FakeReadCause; -use rustc_middle::ty; +use rustc_middle::ty::{self, TypeVisitableExt as _}; use rustc_span::{Symbol, sym}; declare_clippy_lint! { @@ -121,7 +121,15 @@ impl LateLintPass<'_> for ReplaceBox { && let Some(rhs_inner) = get_box_new_payload(cx, rhs) { span_lint_and_then(cx, REPLACE_BOX, expr.span, "creating a new box", |diag| { - let mut app = Applicability::MachineApplicable; + // Replacing the box in place (`*b = ..`) keeps any borrow already stored in it + // alive across the assignment, while `b = Box::new(..)` drops the old box first. + // When the boxed type carries a lifetime, that difference can change + // borrow-check results, so don't offer this as a machine-applicable rewrite. + let mut app = if inner_ty.has_type_flags(ty::TypeFlags::HAS_RE_ERASED) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; let suggestion = format!( "{} = {}", Sugg::hir_with_applicability(cx, lhs, "_", &mut app).deref(), diff --git a/tests/ui/replace_box_unfixable.rs b/tests/ui/replace_box_unfixable.rs new file mode 100644 index 000000000000..aa26f45ee6c2 --- /dev/null +++ b/tests/ui/replace_box_unfixable.rs @@ -0,0 +1,37 @@ +//@no-rustfix: the suggestion can change borrow-check results for boxed types carrying a lifetime +#![warn(clippy::replace_box)] + +// Replacing the box in place keeps any borrow already stored in it alive across the assignment, so +// for a boxed type that carries a lifetime the suggestion is only `MaybeIncorrect`, not applied. + +fn mut_ref() { + let mut a = 300; + let mut b = Box::new(&mut a); + **b += 1; + let mut c = 900; + b = Box::new(&mut c); + //~^ replace_box + **b += 1; +} + +fn shared_ref() { + let x = 1; + let mut b = Box::new(&x); + let y = 2; + b = Box::new(&y); + //~^ replace_box + let _ = **b; +} + +struct Wrapper<'a>(&'a u32); + +fn lifetime_struct() { + let x = 1; + let mut b = Box::new(Wrapper(&x)); + let y = 2; + b = Box::new(Wrapper(&y)); + //~^ replace_box + let _ = *b.0; +} + +fn main() {} diff --git a/tests/ui/replace_box_unfixable.stderr b/tests/ui/replace_box_unfixable.stderr new file mode 100644 index 000000000000..5f5272ee8c9f --- /dev/null +++ b/tests/ui/replace_box_unfixable.stderr @@ -0,0 +1,28 @@ +error: creating a new box + --> tests/ui/replace_box_unfixable.rs:12:5 + | +LL | b = Box::new(&mut c); + | ^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*b = &mut c` + | + = note: this creates a needless allocation + = note: `-D clippy::replace-box` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::replace_box)]` + +error: creating a new box + --> tests/ui/replace_box_unfixable.rs:21:5 + | +LL | b = Box::new(&y); + | ^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*b = &y` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box_unfixable.rs:32:5 + | +LL | b = Box::new(Wrapper(&y)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*b = Wrapper(&y)` + | + = note: this creates a needless allocation + +error: aborting due to 3 previous errors +