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 =
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 =
{$place_name} {$is_partial ->
[true] partially moved

View File

@ -283,7 +283,19 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
Some(name) => format!("`{name}`"),
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
// something that already has `Fn`-like bounds (or is a closure), so we can't
// restrict anyways.
@ -1016,7 +1028,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
None
}
})
&& { true }
&& parent_binop == other_parent_binop
{
// Explicitly look for `expr += other_expr;` and avoid suggesting

View File

@ -5,6 +5,7 @@ use crate::session_diagnostics::{
CaptureVarKind, CaptureVarPathUseCause, OnClosureNote,
};
use rustc_errors::{Applicability, Diag};
use rustc_errors::{DiagCtxt, MultiSpan};
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, Namespace};
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::type_known_to_meet_bound_modulo_regions;
use crate::fluent_generated as fluent;
use super::borrow_set::BorrowData;
use super::MirBorrowckCtxt;
@ -587,7 +590,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn args_subdiag(
self,
dcx: &rustc_errors::DiagCtxt,
dcx: &DiagCtxt,
err: &mut Diag<'_>,
f: impl FnOnce(Span) -> CaptureArgLabel,
) {
@ -601,7 +604,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn var_path_only_subdiag(
self,
dcx: &rustc_errors::DiagCtxt,
dcx: &DiagCtxt,
err: &mut Diag<'_>,
action: crate::InitializationRequiringAction,
) {
@ -639,7 +642,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn var_subdiag(
self,
dcx: &rustc_errors::DiagCtxt,
dcx: &DiagCtxt,
err: &mut Diag<'_>,
kind: Option<rustc_middle::mir::BorrowKind>,
f: impl FnOnce(hir::ClosureKind, Span) -> CaptureVarCause,
@ -1034,7 +1037,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
.map(|n| format!("`{n}`"))
.unwrap_or_else(|| "value".to_owned());
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() =>
{
err.subdiagnostic(
@ -1046,7 +1049,58 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
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, .. } => {
let self_arg = self_arg.unwrap();

View File

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

View File

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