Skip to content

Commit 0f3c293

Browse files
Add suggestion to borrow opaque Fn and FnMut instead of move
1 parent ad5b6f6 commit 0f3c293

File tree

4 files changed

+161
-16
lines changed

4 files changed

+161
-16
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+90-13
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_middle::mir::{
1212
FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef,
1313
ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, VarBindingForm,
1414
};
15-
use rustc_middle::ty::{self, suggest_constraining_type_params, PredicateKind, Ty};
15+
use rustc_middle::ty::{self, subst::Subst, suggest_constraining_type_params, PredicateKind, Ty};
1616
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
1717
use rustc_span::symbol::sym;
1818
use rustc_span::{BytePos, MultiSpan, Span};
@@ -276,22 +276,25 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
276276
}
277277
}
278278

279-
if needs_note {
280-
let opt_name =
281-
self.describe_place_with_options(place.as_ref(), IncludingDowncast(true));
282-
let note_msg = match opt_name {
283-
Some(ref name) => format!("`{}`", name),
284-
None => "value".to_owned(),
285-
};
279+
let opt_name =
280+
self.describe_place_with_options(place.as_ref(), IncludingDowncast(true));
281+
let note_msg = match opt_name {
282+
Some(ref name) => format!("`{}`", name),
283+
None => "value".to_owned(),
284+
};
285+
286+
let tcx = self.infcx.tcx;
287+
let generics = tcx.generics_of(self.mir_def_id());
286288

287-
// Try to find predicates on *generic params* that would allow copying `ty`
288-
let tcx = self.infcx.tcx;
289-
let generics = tcx.generics_of(self.mir_def_id());
289+
if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg) {
290+
// Suppress the next note, since we don't want to put more `Fn`-like bounds onto something that already has them
291+
} else if needs_note {
290292
if let Some(hir_generics) = tcx
291293
.typeck_root_def_id(self.mir_def_id().to_def_id())
292294
.as_local()
293295
.and_then(|def_id| tcx.hir().get_generics(def_id))
294296
{
297+
// Try to find predicates on *generic params* that would allow copying `ty`
295298
let predicates: Result<Vec<_>, _> = tcx.infer_ctxt().enter(|infcx| {
296299
let mut fulfill_cx =
297300
<dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx);
@@ -344,8 +347,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
344347
}
345348

346349
let span = if let Some(local) = place.as_local() {
347-
let decl = &self.body.local_decls[local];
348-
Some(decl.source_info.span)
350+
Some(self.body.local_decls[local].source_info.span)
349351
} else {
350352
None
351353
};
@@ -373,6 +375,81 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
373375
}
374376
}
375377

378+
fn suggest_borrow_fn_like(
379+
&self,
380+
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
381+
ty: Ty<'tcx>,
382+
move_sites: &[MoveSite],
383+
value_name: &str,
384+
) -> bool {
385+
let tcx = self.infcx.tcx;
386+
387+
// Find out if the predicates show that the type is a Fn or FnMut
388+
let find_fn_kind_from_did = |predicates: &[(ty::Predicate<'tcx>, Span)], substs| {
389+
predicates.iter().find_map(|(pred, _)| {
390+
let pred = if let Some(substs) = substs {
391+
pred.subst(tcx, substs).kind().skip_binder()
392+
} else {
393+
pred.kind().skip_binder()
394+
};
395+
if let ty::PredicateKind::Trait(pred) = pred && pred.self_ty() == ty {
396+
if Some(pred.def_id()) == tcx.lang_items().fn_trait() {
397+
return Some(hir::Mutability::Not);
398+
} else if Some(pred.def_id()) == tcx.lang_items().fn_mut_trait() {
399+
return Some(hir::Mutability::Mut);
400+
}
401+
}
402+
None
403+
})
404+
};
405+
406+
// If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
407+
// borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
408+
// These types seem reasonably opaque enough that they could be substituted with their
409+
// borrowed variants in a function body when we see a move error.
410+
let borrow_level = match ty.kind() {
411+
ty::Param(_) => find_fn_kind_from_did(
412+
tcx.explicit_predicates_of(self.mir_def_id().to_def_id()).predicates,
413+
None,
414+
),
415+
ty::Opaque(did, substs) => {
416+
find_fn_kind_from_did(tcx.explicit_item_bounds(*did), Some(*substs))
417+
}
418+
ty::Closure(_, substs) => match substs.as_closure().kind() {
419+
ty::ClosureKind::Fn => Some(hir::Mutability::Not),
420+
ty::ClosureKind::FnMut => Some(hir::Mutability::Mut),
421+
_ => None,
422+
},
423+
_ => None,
424+
};
425+
426+
let Some(borrow_level) = borrow_level else { return false; };
427+
let sugg = move_sites
428+
.iter()
429+
.map(|move_site| {
430+
let move_out = self.move_data.moves[(*move_site).moi];
431+
let moved_place = &self.move_data.move_paths[move_out.path].place;
432+
let move_spans = self.move_spans(moved_place.as_ref(), move_out.source);
433+
let move_span = move_spans.args_or_use();
434+
let suggestion = if borrow_level == hir::Mutability::Mut {
435+
"&mut ".to_string()
436+
} else {
437+
"&".to_string()
438+
};
439+
(move_span.shrink_to_lo(), suggestion)
440+
})
441+
.collect();
442+
err.multipart_suggestion_verbose(
443+
&format!(
444+
"consider {}borrowing {value_name}",
445+
if borrow_level == hir::Mutability::Mut { "mutably " } else { "" }
446+
),
447+
sugg,
448+
Applicability::MaybeIncorrect,
449+
);
450+
true
451+
}
452+
376453
pub(crate) fn report_move_out_while_borrowed(
377454
&mut self,
378455
location: Location,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
fn takes_fn(f: impl Fn()) {
2+
loop {
3+
takes_fnonce(f);
4+
//~^ ERROR use of moved value
5+
//~| HELP consider borrowing
6+
}
7+
}
8+
9+
fn takes_fn_mut(m: impl FnMut()) {
10+
if maybe() {
11+
takes_fnonce(m);
12+
//~^ HELP consider mutably borrowing
13+
}
14+
takes_fnonce(m);
15+
//~^ ERROR use of moved value
16+
}
17+
18+
fn has_closure() {
19+
let mut x = 0;
20+
let closure = || {
21+
x += 1;
22+
};
23+
takes_fnonce(closure);
24+
closure();
25+
}
26+
27+
fn maybe() -> bool {
28+
false
29+
}
30+
31+
// Could also be Fn[Mut], here it doesn't matter
32+
fn takes_fnonce(_: impl FnOnce()) {}
33+
34+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error[E0382]: use of moved value: `f`
2+
--> $DIR/borrow-closures-instead-of-move.rs:3:22
3+
|
4+
LL | fn takes_fn(f: impl Fn()) {
5+
| - move occurs because `f` has type `impl Fn()`, which does not implement the `Copy` trait
6+
LL | loop {
7+
LL | takes_fnonce(f);
8+
| ^ value moved here, in previous iteration of loop
9+
|
10+
help: consider borrowing `f`
11+
|
12+
LL | takes_fnonce(&f);
13+
| +
14+
15+
error[E0382]: use of moved value: `m`
16+
--> $DIR/borrow-closures-instead-of-move.rs:14:18
17+
|
18+
LL | fn takes_fn_mut(m: impl FnMut()) {
19+
| - move occurs because `m` has type `impl FnMut()`, which does not implement the `Copy` trait
20+
LL | if maybe() {
21+
LL | takes_fnonce(m);
22+
| - value moved here
23+
...
24+
LL | takes_fnonce(m);
25+
| ^ value used here after move
26+
|
27+
help: consider mutably borrowing `m`
28+
|
29+
LL | takes_fnonce(&mut m);
30+
| ++++
31+
32+
error: aborting due to 2 previous errors
33+
34+
For more information about this error, try `rustc --explain E0382`.

src/test/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ LL | let mut r = R {c: Box::new(f)};
1717
LL | f(&mut r, false)
1818
| ^ value borrowed here after move
1919
|
20-
help: consider further restricting this bound
20+
help: consider mutably borrowing `f`
2121
|
22-
LL | fn conspirator<F>(mut f: F) where F: FnMut(&mut R, bool) + Copy {
23-
| ++++++
22+
LL | let mut r = R {c: Box::new(&mut f)};
23+
| ++++
2424

2525
error: aborting due to 2 previous errors
2626

0 commit comments

Comments
 (0)