From 690ae523e5c0ac9602db7bbe2b8094ee422e4d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 20 Jul 2025 01:19:55 +0000 Subject: [PATCH 1/5] Tweak borrowck label pointing at `!Copy` value moved into closure When encountering a non-`Copy` value that is moved into a closure which is coming directly from a fn parameter, point at the parameter's type when mentioning it is not `Copy`. Before: ``` error[E0507]: cannot move out of `foo`, a captured variable in an `Fn` closure --> f111.rs:14:25 | 13 | fn do_stuff(foo: Option) { | --- captured outer variable 14 | require_fn_trait(|| async { | -- ^^^^^ `foo` is moved here | | | captured by this `Fn` closure 15 | if foo.map_or(false, |f| f.foo()) { | --- | | | variable moved due to use in coroutine | move occurs because `foo` has type `Option`, which does not implement the `Copy` trait ``` After: ``` error[E0507]: cannot move out of `foo`, a captured variable in an `Fn` closure --> f111.rs:14:25 | 13 | fn do_stuff(foo: Option) { | --- ----------- move occurs because `foo` has type `Option`, which does not implement the `Copy` trait | | | captured outer variable 14 | require_fn_trait(|| async { | -- ^^^^^ `foo` is moved here | | | captured by this `Fn` closure 15 | if foo.map_or(false, |f| f.foo()) { | --- variable moved due to use in coroutine ``` --- .../src/diagnostics/move_errors.rs | 129 +++++++++++------- tests/ui/issues/issue-4335.stderr | 6 +- 2 files changed, 82 insertions(+), 53 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 92ca868eb9925..28024e6fecef4 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -115,10 +115,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { fn append_to_grouped_errors( &self, grouped_errors: &mut Vec>, - error: MoveError<'tcx>, + MoveError { place: original_path, location, kind }: MoveError<'tcx>, ) { - let MoveError { place: original_path, location, kind } = error; - // Note: that the only time we assign a place isn't a temporary // to a user variable is when initializing it. // If that ever stops being the case, then the ever initialized @@ -251,54 +249,47 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } fn report(&mut self, error: GroupedMoveError<'tcx>) { - let (mut err, err_span) = { - let (span, use_spans, original_path, kind) = match error { - GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. } - | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => { - (span, None, original_path, kind) - } - GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => { - (use_spans.args_or_use(), Some(use_spans), original_path, kind) - } - }; - debug!( - "report: original_path={:?} span={:?}, kind={:?} \ - original_path.is_upvar_field_projection={:?}", - original_path, - span, - kind, - self.is_upvar_field_projection(original_path.as_ref()) - ); - if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) { - // If the type may implement Copy, skip the error. - // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check - self.dcx().span_delayed_bug( + let (span, use_spans, original_path, kind) = match error { + GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. } + | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => { + (span, None, original_path, kind) + } + GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => { + (use_spans.args_or_use(), Some(use_spans), original_path, kind) + } + }; + debug!( + "report: original_path={:?} span={:?}, kind={:?} \ + original_path.is_upvar_field_projection={:?}", + original_path, + span, + kind, + self.is_upvar_field_projection(original_path.as_ref()) + ); + if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) { + // If the type may implement Copy, skip the error. + // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check + self.dcx() + .span_delayed_bug(span, "Type may implement copy, but there is no other error."); + return; + } + let mut err = match kind { + &IllegalMoveOriginKind::BorrowedContent { target_place } => self + .report_cannot_move_from_borrowed_content( + original_path, + target_place, span, - "Type may implement copy, but there is no other error.", - ); - return; + use_spans, + ), + &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { + self.cannot_move_out_of_interior_of_drop(span, ty) + } + &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => { + self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index)) } - ( - match kind { - &IllegalMoveOriginKind::BorrowedContent { target_place } => self - .report_cannot_move_from_borrowed_content( - original_path, - target_place, - span, - use_spans, - ), - &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { - self.cannot_move_out_of_interior_of_drop(span, ty) - } - &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => { - self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index)) - } - }, - span, - ) }; - self.add_move_hints(error, &mut err, err_span); + self.add_move_hints(error, &mut err, span); self.buffer_error(err); } @@ -482,7 +473,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.cannot_move_out_of_interior_noncopy(span, ty, None) } ty::Closure(def_id, closure_args) - if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() => + if def_id.as_local() == Some(self.mir_def_id()) + && let Some(upvar_field) = upvar_field => { let closure_kind_ty = closure_args.as_closure().kind_ty(); let closure_kind = match closure_kind_ty.to_opt_closure_kind() { @@ -495,7 +487,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let capture_description = format!("captured variable in an `{closure_kind}` closure"); - let upvar = &self.upvars[upvar_field.unwrap().index()]; + let upvar = &self.upvars[upvar_field.index()]; let upvar_hir_id = upvar.get_root_variable(); let upvar_name = upvar.to_string(tcx); let upvar_span = tcx.hir_span(upvar_hir_id); @@ -604,8 +596,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.add_move_error_details(err, &binds_to); } // No binding. Nothing to suggest. - GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { - let use_span = use_spans.var_or_use(); + GroupedMoveError::OtherIllegalMove { + ref original_path, use_spans, ref kind, .. + } => { + let mut use_span = use_spans.var_or_use(); let place_ty = original_path.ty(self.body, self.infcx.tcx).ty; let place_desc = match self.describe_place(original_path.as_ref()) { Some(desc) => format!("`{desc}`"), @@ -622,6 +616,39 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ); } + if let IllegalMoveOriginKind::BorrowedContent { target_place } = &kind + && let ty = target_place.ty(self.body, self.infcx.tcx).ty + && let ty::Closure(def_id, _) = ty.kind() + && def_id.as_local() == Some(self.mir_def_id()) + && let Some(upvar_field) = self + .prefixes(original_path.as_ref(), PrefixSet::All) + .find_map(|p| self.is_upvar_field_projection(p)) + && let upvar = &self.upvars[upvar_field.index()] + && let upvar_hir_id = upvar.get_root_variable() + && let hir::Node::Param(param) = self.infcx.tcx.parent_hir_node(upvar_hir_id) + { + // Instead of pointing at the path where we access the value within a closure, + // we point at the type on the parameter from the definition of the outer + // function: + // + // error[E0507]: cannot move out of `foo`, a captured + // variable in an `Fn` closure + // --> file.rs:14:25 + // | + // 13 | fn do_stuff(foo: Option) { + // | --- ----------- move occurs because `foo` has type + // | | `Option`, which does not implement + // | | the `Copy` trait + // | captured outer variable + // 14 | require_fn_trait(|| async { + // | -- ^^^^^ `foo` is moved here + // | | + // | captured by this `Fn` closure + // 15 | if foo.map_or(false, |f| f.foo()) { + // | --- variable moved due to use in coroutine + use_span = param.ty_span; + } + err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { is_partial_move: false, ty: place_ty, diff --git a/tests/ui/issues/issue-4335.stderr b/tests/ui/issues/issue-4335.stderr index 14b5cfa9f9ac4..1f1a2595a3189 100644 --- a/tests/ui/issues/issue-4335.stderr +++ b/tests/ui/issues/issue-4335.stderr @@ -2,9 +2,11 @@ error[E0507]: cannot move out of `*v`, as `v` is a captured variable in an `FnMu --> $DIR/issue-4335.rs:6:20 | LL | fn f<'r, T>(v: &'r T) -> Box T + 'r> { - | - captured outer variable + | - ----- move occurs because `*v` has type `T`, which does not implement the `Copy` trait + | | + | captured outer variable LL | id(Box::new(|| *v)) - | -- ^^ move occurs because `*v` has type `T`, which does not implement the `Copy` trait + | -- ^^ | | | captured by this `FnMut` closure | From 5082e6a300974459aec6dc73e76cc039c3a517c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 20 Jul 2025 02:15:45 +0000 Subject: [PATCH 2/5] Generalize logic pointing at binding moved into closure Account not only for `fn` parameters when moving non-`Copy` values into closure, but also for let bindings. ``` error[E0507]: cannot move out of `bar`, a captured variable in an `FnMut` closure --> $DIR/borrowck-move-by-capture.rs:9:29 | LL | let bar: Box<_> = Box::new(3); | --- ------ move occurs because `bar` has type `Box`, which does not implement the `Copy` trait | | | captured outer variable LL | let _g = to_fn_mut(|| { | -- captured by this `FnMut` closure LL | let _h = to_fn_once(move || -> isize { *bar }); | ^^^^^^^^^^^^^^^^ ---- variable moved due to use in closure | | | `bar` is moved here | help: consider cloning the value before moving it into the closure | LL ~ let value = bar.clone(); LL ~ let _h = to_fn_once(move || -> isize { value }); | ``` ``` error[E0507]: cannot move out of `y`, a captured variable in an `Fn` closure --> $DIR/unboxed-closures-move-upvar-from-non-once-ref-closure.rs:12:9 | LL | let y = vec![format!("World")]; | - ---------------------- move occurs because `y` has type `Vec`, which does not implement the `Copy` trait | | | captured outer variable LL | call(|| { | -- captured by this `Fn` closure LL | y.into_iter(); | ^ ----------- `y` moved due to this method call | | | `y` is moved here | note: `into_iter` takes ownership of the receiver `self`, which moves `y` --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL help: you can `clone` the value and consume it, but this might not be your desired behavior | LL | as Clone>::clone(&y).into_iter(); | +++++++++++++++++++++++++++++++ + help: consider cloning the value if the performance cost is acceptable | LL | y.clone().into_iter(); | ++++++++ ``` --- .../src/diagnostics/move_errors.rs | 79 +++++++++++++------ tests/ui/borrowck/borrowck-in-static.stderr | 6 +- .../borrowck/borrowck-move-by-capture.stderr | 10 +-- tests/ui/borrowck/issue-103624.stderr | 7 +- ...ove-upvar-from-non-once-ref-closure.stderr | 6 +- tests/ui/issues/issue-4335.stderr | 2 +- ...-move-out-of-closure-env-issue-1965.stderr | 6 +- ...e-52663-span-decl-captured-variable.stderr | 6 +- ...borrowck-call-is-borrow-issue-12224.stderr | 18 +++-- .../suggestions/option-content-move2.stderr | 18 ++--- .../suggestions/option-content-move3.stderr | 18 ++--- .../unboxed-closure-illegal-move.stderr | 24 ++++-- 12 files changed, 125 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 28024e6fecef4..56f7fe6885441 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -623,30 +623,51 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { && let Some(upvar_field) = self .prefixes(original_path.as_ref(), PrefixSet::All) .find_map(|p| self.is_upvar_field_projection(p)) - && let upvar = &self.upvars[upvar_field.index()] - && let upvar_hir_id = upvar.get_root_variable() - && let hir::Node::Param(param) = self.infcx.tcx.parent_hir_node(upvar_hir_id) { - // Instead of pointing at the path where we access the value within a closure, - // we point at the type on the parameter from the definition of the outer - // function: - // - // error[E0507]: cannot move out of `foo`, a captured - // variable in an `Fn` closure - // --> file.rs:14:25 - // | - // 13 | fn do_stuff(foo: Option) { - // | --- ----------- move occurs because `foo` has type - // | | `Option`, which does not implement - // | | the `Copy` trait - // | captured outer variable - // 14 | require_fn_trait(|| async { - // | -- ^^^^^ `foo` is moved here - // | | - // | captured by this `Fn` closure - // 15 | if foo.map_or(false, |f| f.foo()) { - // | --- variable moved due to use in coroutine - use_span = param.ty_span; + let upvar = &self.upvars[upvar_field.index()]; + let upvar_hir_id = upvar.get_root_variable(); + use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) { + hir::Node::Param(param) => { + // Instead of pointing at the path where we access the value within a + // closure, we point at the type on the parameter from the definition + // of the outer function: + // + // error[E0507]: cannot move out of `foo`, a captured + // variable in an `Fn` closure + // --> file.rs:14:25 + // | + // 13 | fn do_stuff(foo: Option) { + // | --- ----------- move occurs because `foo` has type + // | | `Option`, which does not + // | | implement the `Copy` trait + // | captured outer variable + // 14 | require_fn_trait(|| async { + // | -- ^^^^^ `foo` is moved here + // | | + // | captured by this `Fn` closure + // 15 | if foo.map_or(false, |f| f.foo()) { + // | --- variable moved due to use in coroutine + param.ty_span + } + hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) { + // 13 | fn do_stuff(foo: Option) { + // 14 | let foo: Option = foo; + // | --- ----------- move occurs because `foo` has type + // | | `Option`, which does not implement + // | | the `Copy` trait + // | captured outer variable + (Some(ty), _) => ty.span, + // 13 | fn do_stuff(bar: Option) { + // 14 | let foo = bar; + // | --- --- move occurs because `foo` has type + // | | `Option`, which does not implement the + // | | `Copy` trait + // | captured outer variable + (None, Some(init)) => init.span, + (None, None) => use_span, + }, + _ => use_span, + }; } err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { @@ -656,12 +677,22 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { span: use_span, }); + let mut pointed_at_span = false; use_spans.args_subdiag(err, |args_span| { + if args_span == span || args_span == use_span { + pointed_at_span = true; + } crate::session_diagnostics::CaptureArgLabel::MoveOutPlace { - place: place_desc, + place: place_desc.clone(), args_span, } }); + if !pointed_at_span && use_span != span { + err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace { + place: place_desc, + args_span: span, + }); + } self.add_note_for_packed_struct_derive(err, original_path.local); } diff --git a/tests/ui/borrowck/borrowck-in-static.stderr b/tests/ui/borrowck/borrowck-in-static.stderr index 745b02ae21b81..9bcf64dd62e2a 100644 --- a/tests/ui/borrowck/borrowck-in-static.stderr +++ b/tests/ui/borrowck/borrowck-in-static.stderr @@ -2,9 +2,11 @@ error[E0507]: cannot move out of `x`, a captured variable in an `Fn` closure --> $DIR/borrowck-in-static.rs:5:17 | LL | let x = Box::new(0); - | - captured outer variable + | - ----------- move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | Box::new(|| x) - | -- ^ move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | -- ^ `x` is moved here | | | captured by this `Fn` closure | diff --git a/tests/ui/borrowck/borrowck-move-by-capture.stderr b/tests/ui/borrowck/borrowck-move-by-capture.stderr index 58d5e90e990a2..732af1593d606 100644 --- a/tests/ui/borrowck/borrowck-move-by-capture.stderr +++ b/tests/ui/borrowck/borrowck-move-by-capture.stderr @@ -2,14 +2,14 @@ error[E0507]: cannot move out of `bar`, a captured variable in an `FnMut` closur --> $DIR/borrowck-move-by-capture.rs:9:29 | LL | let bar: Box<_> = Box::new(3); - | --- captured outer variable + | --- ------ move occurs because `bar` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | let _g = to_fn_mut(|| { | -- captured by this `FnMut` closure LL | let _h = to_fn_once(move || -> isize { *bar }); - | ^^^^^^^^^^^^^^^^ ---- - | | | - | | variable moved due to use in closure - | | move occurs because `bar` has type `Box`, which does not implement the `Copy` trait + | ^^^^^^^^^^^^^^^^ ---- variable moved due to use in closure + | | | `bar` is moved here | help: consider cloning the value before moving it into the closure diff --git a/tests/ui/borrowck/issue-103624.stderr b/tests/ui/borrowck/issue-103624.stderr index 603055beadcef..af65deb16dcf8 100644 --- a/tests/ui/borrowck/issue-103624.stderr +++ b/tests/ui/borrowck/issue-103624.stderr @@ -2,13 +2,16 @@ error[E0507]: cannot move out of `self.b`, as `self` is a captured variable in a --> $DIR/issue-103624.rs:16:13 | LL | async fn foo(&self) { - | ----- captured outer variable + | ----- + | | + | captured outer variable + | move occurs because `self.b` has type `StructB`, which does not implement the `Copy` trait LL | let bar = self.b.bar().await; LL | spawn_blocking(move || { | ------- captured by this `Fn` closure LL | LL | self.b; - | ^^^^^^ move occurs because `self.b` has type `StructB`, which does not implement the `Copy` trait + | ^^^^^^ `self.b` is moved here | note: if `StructB` implemented `Clone`, you could clone the value --> $DIR/issue-103624.rs:23:1 diff --git a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr index 177e9c8d2487e..da0e133ba023e 100644 --- a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr +++ b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr @@ -2,13 +2,15 @@ error[E0507]: cannot move out of `y`, a captured variable in an `Fn` closure --> $DIR/unboxed-closures-move-upvar-from-non-once-ref-closure.rs:12:9 | LL | let y = vec![format!("World")]; - | - captured outer variable + | - ---------------------- move occurs because `y` has type `Vec`, which does not implement the `Copy` trait + | | + | captured outer variable LL | call(|| { | -- captured by this `Fn` closure LL | y.into_iter(); | ^ ----------- `y` moved due to this method call | | - | move occurs because `y` has type `Vec`, which does not implement the `Copy` trait + | `y` is moved here | note: `into_iter` takes ownership of the receiver `self`, which moves `y` --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL diff --git a/tests/ui/issues/issue-4335.stderr b/tests/ui/issues/issue-4335.stderr index 1f1a2595a3189..42ac632256401 100644 --- a/tests/ui/issues/issue-4335.stderr +++ b/tests/ui/issues/issue-4335.stderr @@ -6,7 +6,7 @@ LL | fn f<'r, T>(v: &'r T) -> Box T + 'r> { | | | captured outer variable LL | id(Box::new(|| *v)) - | -- ^^ + | -- ^^ `*v` is moved here | | | captured by this `FnMut` closure | diff --git a/tests/ui/moves/moves-based-on-type-move-out-of-closure-env-issue-1965.stderr b/tests/ui/moves/moves-based-on-type-move-out-of-closure-env-issue-1965.stderr index 523134a9425f4..51d0f85c031f5 100644 --- a/tests/ui/moves/moves-based-on-type-move-out-of-closure-env-issue-1965.stderr +++ b/tests/ui/moves/moves-based-on-type-move-out-of-closure-env-issue-1965.stderr @@ -2,9 +2,11 @@ error[E0507]: cannot move out of `i`, a captured variable in an `Fn` closure --> $DIR/moves-based-on-type-move-out-of-closure-env-issue-1965.rs:9:28 | LL | let i = Box::new(3); - | - captured outer variable + | - ----------- move occurs because `i` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | let _f = to_fn(|| test(i)); - | -- ^ move occurs because `i` has type `Box`, which does not implement the `Copy` trait + | -- ^ `i` is moved here | | | captured by this `Fn` closure | diff --git a/tests/ui/nll/issue-52663-span-decl-captured-variable.stderr b/tests/ui/nll/issue-52663-span-decl-captured-variable.stderr index fbaec8a6008a1..5754603700653 100644 --- a/tests/ui/nll/issue-52663-span-decl-captured-variable.stderr +++ b/tests/ui/nll/issue-52663-span-decl-captured-variable.stderr @@ -2,9 +2,11 @@ error[E0507]: cannot move out of `x.0`, as `x` is a captured variable in an `Fn` --> $DIR/issue-52663-span-decl-captured-variable.rs:8:26 | LL | let x = (vec![22], vec![44]); - | - captured outer variable + | - -------------------- move occurs because `x.0` has type `Vec`, which does not implement the `Copy` trait + | | + | captured outer variable LL | expect_fn(|| drop(x.0)); - | -- ^^^ move occurs because `x.0` has type `Vec`, which does not implement the `Copy` trait + | -- ^^^ `x.0` is moved here | | | captured by this `Fn` closure | diff --git a/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr b/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr index f37dc320fa315..2876169241396 100644 --- a/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr +++ b/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr @@ -35,14 +35,18 @@ LL | fn test4(f: &mut Test) { error[E0507]: cannot move out of `f`, a captured variable in an `FnMut` closure --> $DIR/borrowck-call-is-borrow-issue-12224.rs:57:13 | -LL | let mut f = move |g: Box, b: isize| { - | ----- captured outer variable -... -LL | f(Box::new(|a| { - | --- captured by this `FnMut` closure +LL | let mut f = move |g: Box, b: isize| { + | _________-----___- + | | | + | | captured outer variable +LL | | let _ = s.len(); +LL | | }; + | |_____- move occurs because `f` has type `{closure@$DIR/borrowck-call-is-borrow-issue-12224.rs:52:17: 52:58}`, which does not implement the `Copy` trait +LL | f(Box::new(|a| { + | --- captured by this `FnMut` closure LL | -LL | foo(f); - | ^ move occurs because `f` has type `{closure@$DIR/borrowck-call-is-borrow-issue-12224.rs:52:17: 52:58}`, which does not implement the `Copy` trait +LL | foo(f); + | ^ `f` is moved here | help: consider cloning the value if the performance cost is acceptable | diff --git a/tests/ui/suggestions/option-content-move2.stderr b/tests/ui/suggestions/option-content-move2.stderr index be97cba17b900..436441d6f1b9e 100644 --- a/tests/ui/suggestions/option-content-move2.stderr +++ b/tests/ui/suggestions/option-content-move2.stderr @@ -2,7 +2,9 @@ error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closur --> $DIR/option-content-move2.rs:11:9 | LL | let mut var = None; - | ------- captured outer variable + | ------- ---- move occurs because `var` has type `Option`, which does not implement the `Copy` trait + | | + | captured outer variable LL | func(|| { | -- captured by this `FnMut` closure LL | // Shouldn't suggest `move ||.as_ref()` here @@ -10,16 +12,15 @@ LL | move || { | ^^^^^^^ `var` is moved here LL | LL | var = Some(NotCopyable); - | --- - | | - | variable moved due to use in closure - | move occurs because `var` has type `Option`, which does not implement the `Copy` trait + | --- variable moved due to use in closure error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure --> $DIR/option-content-move2.rs:21:9 | LL | let mut var = None; - | ------- captured outer variable + | ------- ---- move occurs because `var` has type `Option`, which does not implement the `Copy` trait + | | + | captured outer variable LL | func(|| { | -- captured by this `FnMut` closure LL | // Shouldn't suggest `move ||.as_ref()` nor to `clone()` here @@ -27,10 +28,7 @@ LL | move || { | ^^^^^^^ `var` is moved here LL | LL | var = Some(NotCopyableButCloneable); - | --- - | | - | variable moved due to use in closure - | move occurs because `var` has type `Option`, which does not implement the `Copy` trait + | --- variable moved due to use in closure error: aborting due to 2 previous errors diff --git a/tests/ui/suggestions/option-content-move3.stderr b/tests/ui/suggestions/option-content-move3.stderr index faaf8a9df9d72..68c52352a6512 100644 --- a/tests/ui/suggestions/option-content-move3.stderr +++ b/tests/ui/suggestions/option-content-move3.stderr @@ -26,17 +26,16 @@ error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closur --> $DIR/option-content-move3.rs:12:9 | LL | let var = NotCopyable; - | --- captured outer variable + | --- ----------- move occurs because `var` has type `NotCopyable`, which does not implement the `Copy` trait + | | + | captured outer variable LL | func(|| { | -- captured by this `FnMut` closure LL | // Shouldn't suggest `move ||.as_ref()` here LL | move || { | ^^^^^^^ `var` is moved here LL | let x = var; - | --- - | | - | variable moved due to use in closure - | move occurs because `var` has type `NotCopyable`, which does not implement the `Copy` trait + | --- variable moved due to use in closure | note: if `NotCopyable` implemented `Clone`, you could clone the value --> $DIR/option-content-move3.rs:2:1 @@ -67,17 +66,16 @@ error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closur --> $DIR/option-content-move3.rs:23:9 | LL | let var = NotCopyableButCloneable; - | --- captured outer variable + | --- ----------------------- move occurs because `var` has type `NotCopyableButCloneable`, which does not implement the `Copy` trait + | | + | captured outer variable LL | func(|| { | -- captured by this `FnMut` closure LL | // Shouldn't suggest `move ||.as_ref()` here LL | move || { | ^^^^^^^ `var` is moved here LL | let x = var; - | --- - | | - | variable moved due to use in closure - | move occurs because `var` has type `NotCopyableButCloneable`, which does not implement the `Copy` trait + | --- variable moved due to use in closure | help: consider cloning the value before moving it into the closure | diff --git a/tests/ui/unboxed-closures/unboxed-closure-illegal-move.stderr b/tests/ui/unboxed-closures/unboxed-closure-illegal-move.stderr index cf4391311d03d..8d9a61cb68126 100644 --- a/tests/ui/unboxed-closures/unboxed-closure-illegal-move.stderr +++ b/tests/ui/unboxed-closures/unboxed-closure-illegal-move.stderr @@ -2,9 +2,11 @@ error[E0507]: cannot move out of `x`, a captured variable in an `Fn` closure --> $DIR/unboxed-closure-illegal-move.rs:15:31 | LL | let x = Box::new(0); - | - captured outer variable + | - ----------- move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | let f = to_fn(|| drop(x)); - | -- ^ move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | -- ^ `x` is moved here | | | captured by this `Fn` closure | @@ -17,9 +19,11 @@ error[E0507]: cannot move out of `x`, a captured variable in an `FnMut` closure --> $DIR/unboxed-closure-illegal-move.rs:19:35 | LL | let x = Box::new(0); - | - captured outer variable + | - ----------- move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | let f = to_fn_mut(|| drop(x)); - | -- ^ move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | -- ^ `x` is moved here | | | captured by this `FnMut` closure | @@ -32,9 +36,11 @@ error[E0507]: cannot move out of `x`, a captured variable in an `Fn` closure --> $DIR/unboxed-closure-illegal-move.rs:28:36 | LL | let x = Box::new(0); - | - captured outer variable + | - ----------- move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | let f = to_fn(move || drop(x)); - | ------- ^ move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | ------- ^ `x` is moved here | | | captured by this `Fn` closure @@ -42,9 +48,11 @@ error[E0507]: cannot move out of `x`, a captured variable in an `FnMut` closure --> $DIR/unboxed-closure-illegal-move.rs:32:40 | LL | let x = Box::new(0); - | - captured outer variable + | - ----------- move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | | + | captured outer variable LL | let f = to_fn_mut(move || drop(x)); - | ------- ^ move occurs because `x` has type `Box`, which does not implement the `Copy` trait + | ------- ^ `x` is moved here | | | captured by this `FnMut` closure From 8df93e6966e71da8a249a0022680b83eff105f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 20 Jul 2025 02:22:55 +0000 Subject: [PATCH 3/5] Tweak spans when encountering multiline initializer in move error ``` error[E0507]: cannot move out of `f`, a captured variable in an `FnMut` closure --> $DIR/borrowck-call-is-borrow-issue-12224.rs:57:13 | LL | let mut f = move |g: Box, b: isize| { | ----- captured outer variable ... LL | f(Box::new(|a| { | --- captured by this `FnMut` closure LL | LL | foo(f); | ^ move occurs because `f` has type `{closure@$DIR/borrowck-call-is-borrow-issue-12224.rs:52:17: 52:58}`, which does not implement the `Copy` trait ``` instead of ``` error[E0507]: cannot move out of `f`, a captured variable in an `FnMut` closure --> $DIR/borrowck-call-is-borrow-issue-12224.rs:57:13 | LL | let mut f = move |g: Box, b: isize| { | _________-----___- | | | | | captured outer variable LL | | let _ = s.len(); LL | | }; | |_____- move occurs because `f` has type `{closure@$DIR/borrowck-call-is-borrow-issue-12224.rs:52:17: 52:58}`, which does not implement the `Copy` trait LL | f(Box::new(|a| { | --- captured by this `FnMut` closure LL | LL | foo(f); | ^ `f` is moved here ``` --- .../src/diagnostics/move_errors.rs | 11 +++++++++-- ...move-upvar-from-non-once-ref-closure.stderr | 6 ++---- .../borrowck-call-is-borrow-issue-12224.stderr | 18 +++++++----------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 56f7fe6885441..40361906e0423 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -663,8 +663,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // | | `Option`, which does not implement the // | | `Copy` trait // | captured outer variable - (None, Some(init)) => init.span, - (None, None) => use_span, + // + // We don't want the case where the initializer is something that spans + // multiple lines, like a closure, as the ASCII art gets messy. + (None, Some(init)) + if !self.infcx.tcx.sess.source_map().is_multiline(init.span) => + { + init.span + } + _ => use_span, }, _ => use_span, }; diff --git a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr index da0e133ba023e..177e9c8d2487e 100644 --- a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr +++ b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr @@ -2,15 +2,13 @@ error[E0507]: cannot move out of `y`, a captured variable in an `Fn` closure --> $DIR/unboxed-closures-move-upvar-from-non-once-ref-closure.rs:12:9 | LL | let y = vec![format!("World")]; - | - ---------------------- move occurs because `y` has type `Vec`, which does not implement the `Copy` trait - | | - | captured outer variable + | - captured outer variable LL | call(|| { | -- captured by this `Fn` closure LL | y.into_iter(); | ^ ----------- `y` moved due to this method call | | - | `y` is moved here + | move occurs because `y` has type `Vec`, which does not implement the `Copy` trait | note: `into_iter` takes ownership of the receiver `self`, which moves `y` --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL diff --git a/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr b/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr index 2876169241396..f37dc320fa315 100644 --- a/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr +++ b/tests/ui/span/borrowck-call-is-borrow-issue-12224.stderr @@ -35,18 +35,14 @@ LL | fn test4(f: &mut Test) { error[E0507]: cannot move out of `f`, a captured variable in an `FnMut` closure --> $DIR/borrowck-call-is-borrow-issue-12224.rs:57:13 | -LL | let mut f = move |g: Box, b: isize| { - | _________-----___- - | | | - | | captured outer variable -LL | | let _ = s.len(); -LL | | }; - | |_____- move occurs because `f` has type `{closure@$DIR/borrowck-call-is-borrow-issue-12224.rs:52:17: 52:58}`, which does not implement the `Copy` trait -LL | f(Box::new(|a| { - | --- captured by this `FnMut` closure +LL | let mut f = move |g: Box, b: isize| { + | ----- captured outer variable +... +LL | f(Box::new(|a| { + | --- captured by this `FnMut` closure LL | -LL | foo(f); - | ^ `f` is moved here +LL | foo(f); + | ^ move occurs because `f` has type `{closure@$DIR/borrowck-call-is-borrow-issue-12224.rs:52:17: 52:58}`, which does not implement the `Copy` trait | help: consider cloning the value if the performance cost is acceptable | From dafc9f9b534c8d1a50ba87f776eb1d6dfb1a7c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 21 Jul 2025 16:23:13 +0000 Subject: [PATCH 4/5] Reduce comment verbosity --- .../src/diagnostics/move_errors.rs | 38 +++---------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 40361906e0423..67dce7615c0df 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -629,43 +629,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) { hir::Node::Param(param) => { // Instead of pointing at the path where we access the value within a - // closure, we point at the type on the parameter from the definition - // of the outer function: - // - // error[E0507]: cannot move out of `foo`, a captured - // variable in an `Fn` closure - // --> file.rs:14:25 - // | - // 13 | fn do_stuff(foo: Option) { - // | --- ----------- move occurs because `foo` has type - // | | `Option`, which does not - // | | implement the `Copy` trait - // | captured outer variable - // 14 | require_fn_trait(|| async { - // | -- ^^^^^ `foo` is moved here - // | | - // | captured by this `Fn` closure - // 15 | if foo.map_or(false, |f| f.foo()) { - // | --- variable moved due to use in coroutine + // closure, we point at the type of the outer `fn` argument. param.ty_span } hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) { - // 13 | fn do_stuff(foo: Option) { - // 14 | let foo: Option = foo; - // | --- ----------- move occurs because `foo` has type - // | | `Option`, which does not implement - // | | the `Copy` trait - // | captured outer variable + // We point at the type of the outer let-binding. (Some(ty), _) => ty.span, - // 13 | fn do_stuff(bar: Option) { - // 14 | let foo = bar; - // | --- --- move occurs because `foo` has type - // | | `Option`, which does not implement the - // | | `Copy` trait - // | captured outer variable - // - // We don't want the case where the initializer is something that spans - // multiple lines, like a closure, as the ASCII art gets messy. + // We point at the initializer of the outer let-binding, but only if it + // isn't something that spans multiple lines, like a closure, as the + // ASCII art gets messy. (None, Some(init)) if !self.infcx.tcx.sess.source_map().is_multiline(init.span) => { From 6237e735c4dc165b3efec236e11a44bdccf1dfd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 21 Jul 2025 16:53:31 +0000 Subject: [PATCH 5/5] Point at the type that doesn't impl `Clone` in more cases beyond closures --- .../rustc_borrowck/src/diagnostics/move_errors.rs | 15 +++++---------- .../closure-shim-borrowck-error.stderr | 7 +++---- .../async-closures/move-out-of-ref.stderr | 5 ++++- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 67dce7615c0df..a0c1f66d35c2f 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -596,9 +596,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.add_move_error_details(err, &binds_to); } // No binding. Nothing to suggest. - GroupedMoveError::OtherIllegalMove { - ref original_path, use_spans, ref kind, .. - } => { + GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { let mut use_span = use_spans.var_or_use(); let place_ty = original_path.ty(self.body, self.infcx.tcx).ty; let place_desc = match self.describe_place(original_path.as_ref()) { @@ -616,14 +614,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ); } - if let IllegalMoveOriginKind::BorrowedContent { target_place } = &kind - && let ty = target_place.ty(self.body, self.infcx.tcx).ty - && let ty::Closure(def_id, _) = ty.kind() - && def_id.as_local() == Some(self.mir_def_id()) - && let Some(upvar_field) = self - .prefixes(original_path.as_ref(), PrefixSet::All) - .find_map(|p| self.is_upvar_field_projection(p)) + if let Some(upvar_field) = self + .prefixes(original_path.as_ref(), PrefixSet::All) + .find_map(|p| self.is_upvar_field_projection(p)) { + // Look for the introduction of the original binding being moved. let upvar = &self.upvars[upvar_field.index()]; let upvar_hir_id = upvar.get_root_variable(); use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) { diff --git a/tests/ui/async-await/async-closures/closure-shim-borrowck-error.stderr b/tests/ui/async-await/async-closures/closure-shim-borrowck-error.stderr index 03fa220b0bfae..3fe1431fda714 100644 --- a/tests/ui/async-await/async-closures/closure-shim-borrowck-error.stderr +++ b/tests/ui/async-await/async-closures/closure-shim-borrowck-error.stderr @@ -1,14 +1,13 @@ error[E0507]: cannot move out of `x` which is behind a mutable reference --> $DIR/closure-shim-borrowck-error.rs:11:18 | +LL | fn hello(x: Ty) { + | -- move occurs because `x` has type `Ty`, which does not implement the `Copy` trait LL | needs_fn_mut(async || { | ^^^^^^^^ `x` is moved here LL | LL | x.hello(); - | - - | | - | variable moved due to use in coroutine - | move occurs because `x` has type `Ty`, which does not implement the `Copy` trait + | - variable moved due to use in coroutine | note: if `Ty` implemented `Clone`, you could clone the value --> $DIR/closure-shim-borrowck-error.rs:17:1 diff --git a/tests/ui/async-await/async-closures/move-out-of-ref.stderr b/tests/ui/async-await/async-closures/move-out-of-ref.stderr index 8a63515a8a908..d443dc9d4831b 100644 --- a/tests/ui/async-await/async-closures/move-out-of-ref.stderr +++ b/tests/ui/async-await/async-closures/move-out-of-ref.stderr @@ -1,8 +1,11 @@ error[E0507]: cannot move out of `*x` which is behind a shared reference --> $DIR/move-out-of-ref.rs:9:9 | +LL | fn hello(x: &Ty) { + | --- move occurs because `*x` has type `Ty`, which does not implement the `Copy` trait +LL | let c = async || { LL | *x; - | ^^ move occurs because `*x` has type `Ty`, which does not implement the `Copy` trait + | ^^ `*x` is moved here | note: if `Ty` implemented `Clone`, you could clone the value --> $DIR/move-out-of-ref.rs:5:1