Better account for FnOnce in move errors

```
error[E0382]: use of moved value: `blk`
  --> $DIR/once-cant-call-twice-on-heap.rs:8:5
   |
LL | fn foo<F:FnOnce()>(blk: F) {
   |                    --- move occurs because `blk` has type `F`, which does not implement the `Copy` trait
LL |     blk();
   |     ----- `blk` moved due to this call
LL |     blk();
   |     ^^^ value used here after move
   |
note: `FnOnce` closures can only be called once
  --> $DIR/once-cant-call-twice-on-heap.rs:6:10
   |
LL | fn foo<F:FnOnce()>(blk: F) {
   |          ^^^^^^^^ `F` is made to be an `FnOnce` closure here
LL |     blk();
   |     ----- this value implements `FnOnce`, which causes it to be moved when called
```
This commit is contained in:
Esteban Küber 2024-03-15 18:08:12 +00:00
parent dfe28debb9
commit 4ca876b7a4
5 changed files with 88 additions and 21 deletions

View File

@ -87,6 +87,12 @@ borrowck_move_unsized =
borrowck_moved_a_fn_once_in_call = borrowck_moved_a_fn_once_in_call =
this value implements `FnOnce`, which causes it to be moved when called this value implements `FnOnce`, which causes it to be moved when called
borrowck_moved_a_fn_once_in_call_call =
`FnOnce` closures can only be called once
borrowck_moved_a_fn_once_in_call_def =
`{$ty}` is made to be an `FnOnce` closure here
borrowck_moved_due_to_await = borrowck_moved_due_to_await =
{$place_name} {$is_partial -> {$place_name} {$is_partial ->
[true] partially moved [true] partially moved

View File

@ -283,7 +283,19 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
Some(name) => format!("`{name}`"), Some(name) => format!("`{name}`"),
None => "value".to_owned(), None => "value".to_owned(),
}; };
if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg) { if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg)
|| if let UseSpans::FnSelfUse { kind, .. } = use_spans
&& let CallKind::FnCall { fn_trait_id, self_ty } = kind
&& let ty::Param(_) = self_ty.kind()
&& ty == self_ty
&& Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait()
{
// this is a type parameter `T: FnOnce()`, don't suggest `T: FnOnce() + Clone`.
true
} else {
false
}
{
// Suppress the next suggestion since we don't want to put more bounds onto // Suppress the next suggestion since we don't want to put more bounds onto
// something that already has `Fn`-like bounds (or is a closure), so we can't // something that already has `Fn`-like bounds (or is a closure), so we can't
// restrict anyways. // restrict anyways.
@ -1016,7 +1028,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
None None
} }
}) })
&& { true }
&& parent_binop == other_parent_binop && parent_binop == other_parent_binop
{ {
// Explicitly look for `expr += other_expr;` and avoid suggesting // Explicitly look for `expr += other_expr;` and avoid suggesting

View File

@ -5,6 +5,7 @@ use crate::session_diagnostics::{
CaptureVarKind, CaptureVarPathUseCause, OnClosureNote, CaptureVarKind, CaptureVarPathUseCause, OnClosureNote,
}; };
use rustc_errors::{Applicability, Diag}; use rustc_errors::{Applicability, Diag};
use rustc_errors::{DiagCtxt, MultiSpan};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::{CtorKind, Namespace}; use rustc_hir::def::{CtorKind, Namespace};
use rustc_hir::CoroutineKind; use rustc_hir::CoroutineKind;
@ -29,6 +30,8 @@ use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _; use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions; use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions;
use crate::fluent_generated as fluent;
use super::borrow_set::BorrowData; use super::borrow_set::BorrowData;
use super::MirBorrowckCtxt; use super::MirBorrowckCtxt;
@ -587,7 +590,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)] #[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn args_subdiag( pub(super) fn args_subdiag(
self, self,
dcx: &rustc_errors::DiagCtxt, dcx: &DiagCtxt,
err: &mut Diag<'_>, err: &mut Diag<'_>,
f: impl FnOnce(Span) -> CaptureArgLabel, f: impl FnOnce(Span) -> CaptureArgLabel,
) { ) {
@ -601,7 +604,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)] #[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn var_path_only_subdiag( pub(super) fn var_path_only_subdiag(
self, self,
dcx: &rustc_errors::DiagCtxt, dcx: &DiagCtxt,
err: &mut Diag<'_>, err: &mut Diag<'_>,
action: crate::InitializationRequiringAction, action: crate::InitializationRequiringAction,
) { ) {
@ -639,7 +642,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)] #[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn var_subdiag( pub(super) fn var_subdiag(
self, self,
dcx: &rustc_errors::DiagCtxt, dcx: &DiagCtxt,
err: &mut Diag<'_>, err: &mut Diag<'_>,
kind: Option<rustc_middle::mir::BorrowKind>, kind: Option<rustc_middle::mir::BorrowKind>,
f: impl FnOnce(hir::ClosureKind, Span) -> CaptureVarCause, f: impl FnOnce(hir::ClosureKind, Span) -> CaptureVarCause,
@ -1034,7 +1037,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
.map(|n| format!("`{n}`")) .map(|n| format!("`{n}`"))
.unwrap_or_else(|| "value".to_owned()); .unwrap_or_else(|| "value".to_owned());
match kind { match kind {
CallKind::FnCall { fn_trait_id, .. } CallKind::FnCall { fn_trait_id, self_ty }
if Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait() => if Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait() =>
{ {
err.subdiagnostic( err.subdiagnostic(
@ -1046,7 +1049,58 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
is_loop_message, is_loop_message,
}, },
); );
err.subdiagnostic(self.dcx(), CaptureReasonNote::FnOnceMoveInCall { var_span }); if let ty::Param(param_ty) = self_ty.kind()
&& let generics = self.infcx.tcx.generics_of(self.mir_def_id())
&& let param = generics.type_param(param_ty, self.infcx.tcx)
&& let Some(hir_generics) = self
.infcx
.tcx
.typeck_root_def_id(self.mir_def_id().to_def_id())
.as_local()
.and_then(|def_id| self.infcx.tcx.hir().get_generics(def_id))
&& let spans = hir_generics
.predicates
.iter()
.filter_map(|pred| match pred {
hir::WherePredicate::BoundPredicate(pred) => Some(pred),
_ => None,
})
.filter(|pred| {
if let Some((id, _)) = pred.bounded_ty.as_generic_param() {
id == param.def_id
} else {
false
}
})
.flat_map(|pred| pred.bounds)
.filter_map(|bound| {
if let Some(trait_ref) = bound.trait_ref()
&& let Some(trait_def_id) = trait_ref.trait_def_id()
&& trait_def_id == fn_trait_id
{
Some(bound.span())
} else {
None
}
})
.collect::<Vec<Span>>()
&& !spans.is_empty()
{
let mut span: MultiSpan = spans.clone().into();
for sp in spans {
span.push_span_label(sp, fluent::borrowck_moved_a_fn_once_in_call_def);
}
span.push_span_label(
fn_call_span,
fluent::borrowck_moved_a_fn_once_in_call,
);
err.span_note(span, fluent::borrowck_moved_a_fn_once_in_call_call);
} else {
err.subdiagnostic(
self.dcx(),
CaptureReasonNote::FnOnceMoveInCall { var_span },
);
}
} }
CallKind::Operator { self_arg, trait_id, .. } => { CallKind::Operator { self_arg, trait_id, .. } => {
let self_arg = self_arg.unwrap(); let self_arg = self_arg.unwrap();

View File

@ -29,15 +29,13 @@ LL | f(1, 2);
LL | f(1, 2); LL | f(1, 2);
| ^ value used here after move | ^ value used here after move
| |
note: this value implements `FnOnce`, which causes it to be moved when called note: `FnOnce` closures can only be called once
--> $DIR/borrowck-unboxed-closures.rs:11:5 --> $DIR/borrowck-unboxed-closures.rs:10:8
| |
LL | fn c<F:FnOnce(isize, isize) -> isize>(f: F) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `F` is made to be an `FnOnce` closure here
LL | f(1, 2); LL | f(1, 2);
| ^ | ------- this value implements `FnOnce`, which causes it to be moved when called
help: consider further restricting this bound
|
LL | fn c<F:FnOnce(isize, isize) -> isize + Copy>(f: F) {
| ++++++
error: aborting due to 3 previous errors error: aborting due to 3 previous errors

View File

@ -8,15 +8,13 @@ LL | blk();
LL | blk(); LL | blk();
| ^^^ value used here after move | ^^^ value used here after move
| |
note: this value implements `FnOnce`, which causes it to be moved when called note: `FnOnce` closures can only be called once
--> $DIR/once-cant-call-twice-on-heap.rs:7:5 --> $DIR/once-cant-call-twice-on-heap.rs:6:10
| |
LL | fn foo<F:FnOnce()>(blk: F) {
| ^^^^^^^^ `F` is made to be an `FnOnce` closure here
LL | blk(); LL | blk();
| ^^^ | ----- this value implements `FnOnce`, which causes it to be moved when called
help: consider further restricting this bound
|
LL | fn foo<F:FnOnce() + Copy>(blk: F) {
| ++++++
error: aborting due to 1 previous error error: aborting due to 1 previous error