diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 248396ba9849b..8547f7593232e 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -34,6 +34,7 @@ use tracing::debug; use super::MirBorrowckCtxt; use super::borrow_set::BorrowData; +use crate::LocalMutationIsAllowed; use crate::constraints::OutlivesConstraint; use crate::nll::ConstraintDescription; use crate::session_diagnostics::{ @@ -1426,11 +1427,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { if let ty::Ref(_, _, hir::Mutability::Mut) = moved_place.ty(self.body, self.infcx.tcx).ty.kind() { + // The `&mut *place` reborrow suggestion is `MachineApplicable`, so + // only offer it where `*place` can be borrowed mutably: a value + // captured by an `Fn` closure (held via `&self`) cannot, and the + // suggestion would otherwise fail to compile with E0596. + let reborrow_place = self.infcx.tcx.mk_place_deref(moved_place); + let reborrow_is_valid = self + .is_mutable(reborrow_place.as_ref(), LocalMutationIsAllowed::No) + .is_ok(); // Suggest `reborrow` in other place for following situations: // 1. If we are in a loop this will be suggested later. // 2. If the moved value is a mut reference, it is used in a // generic function and the corresponding arg's type is generic param. - if !is_loop_move && !has_suggest_reborrow { + if !is_loop_move && !has_suggest_reborrow && reborrow_is_valid { self.suggest_reborrow( err, move_span.shrink_to_lo(), diff --git a/tests/ui/borrowck/fn-closure-move-capture-no-reborrow-sugg.rs b/tests/ui/borrowck/fn-closure-move-capture-no-reborrow-sugg.rs new file mode 100644 index 0000000000000..4f0c65dd5bd2a --- /dev/null +++ b/tests/ui/borrowck/fn-closure-move-capture-no-reborrow-sugg.rs @@ -0,0 +1,46 @@ +//@ edition: 2021 +//@ compile-flags: --crate-type=lib + +// Regression test for https://github.com/rust-lang/rust/issues/150175. +// +// Moving a `&mut`-typed captured variable out of a closure (here through the +// implicit `into_iter` of a `for` loop) is an E0507. The "consider creating a +// fresh reborrow" suggestion rewrites the move site to `&mut *foos`, but that +// only compiles when the capture can be borrowed mutably there: +// +// * an `Fn` closure holds its captures through `&self`, so `&mut *foos` +// cannot be borrowed (E0596). The suggestion is `MachineApplicable`, so +// offering it here produces code that does not compile -- it must be +// suppressed (this is the bug). +// * an `FnMut` closure holds its captures through `&mut self`, so the +// reborrow is valid and the suggestion must still be offered. + +pub struct Foo; + +pub fn in_fn Result<(), ()>>(f: F) -> Result<(), ()> { + f() +} + +pub fn in_fn_mut Result<(), ()>>(mut f: F) -> Result<(), ()> { + f() +} + +// `Fn` closure: a mutable reborrow is impossible, so no suggestion is offered. +pub fn fn_closure(foos: &mut [&mut Foo]) -> Result<(), ()> { + in_fn(|| { + for _ in foos { + //~^ ERROR cannot move out of `foos`, a captured variable in an `Fn` closure + } + Ok(()) + }) +} + +// `FnMut` closure: a mutable reborrow is valid, so the suggestion is offered. +pub fn fn_mut_closure(foos: &mut [&mut Foo]) -> Result<(), ()> { + in_fn_mut(|| { + for _ in foos { + //~^ ERROR cannot move out of `foos`, a captured variable in an `FnMut` closure + } + Ok(()) + }) +} diff --git a/tests/ui/borrowck/fn-closure-move-capture-no-reborrow-sugg.stderr b/tests/ui/borrowck/fn-closure-move-capture-no-reborrow-sugg.stderr new file mode 100644 index 0000000000000..e31bae3a4ddf6 --- /dev/null +++ b/tests/ui/borrowck/fn-closure-move-capture-no-reborrow-sugg.stderr @@ -0,0 +1,53 @@ +error[E0507]: cannot move out of `foos`, a captured variable in an `Fn` closure + --> $DIR/fn-closure-move-capture-no-reborrow-sugg.rs:31:18 + | +LL | pub fn fn_closure(foos: &mut [&mut Foo]) -> Result<(), ()> { + | ---- --------------- move occurs because `foos` has type `&mut [&mut Foo]`, which does not implement the `Copy` trait + | | + | captured outer variable +LL | in_fn(|| { + | -- captured by this `Fn` closure +LL | for _ in foos { + | ^^^^ + | | + | `foos` moved due to this implicit call to `.into_iter()` + | `foos` is moved here + | +help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but `FnOnce` closures may consume them only once + --> $DIR/fn-closure-move-capture-no-reborrow-sugg.rs:20:17 + | +LL | pub fn in_fn Result<(), ()>>(f: F) -> Result<(), ()> { + | ^^^^^^^^^^^^^^^^^^^^^^ +note: `into_iter` takes ownership of the receiver `self`, which moves `foos` + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL + +error[E0507]: cannot move out of `foos`, a captured variable in an `FnMut` closure + --> $DIR/fn-closure-move-capture-no-reborrow-sugg.rs:41:18 + | +LL | pub fn fn_mut_closure(foos: &mut [&mut Foo]) -> Result<(), ()> { + | ---- --------------- move occurs because `foos` has type `&mut [&mut Foo]`, which does not implement the `Copy` trait + | | + | captured outer variable +LL | in_fn_mut(|| { + | -- captured by this `FnMut` closure +LL | for _ in foos { + | ^^^^ + | | + | `foos` moved due to this implicit call to `.into_iter()` + | `foos` is moved here + | +help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but `FnOnce` closures may consume them only once + --> $DIR/fn-closure-move-capture-no-reborrow-sugg.rs:24:21 + | +LL | pub fn in_fn_mut Result<(), ()>>(mut f: F) -> Result<(), ()> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +note: `into_iter` takes ownership of the receiver `self`, which moves `foos` + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL +help: consider creating a fresh reborrow of `foos` here + | +LL | for _ in &mut *foos { + | ++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0507`.