Skip to content

Commit abf0ec8

Browse files
committed
Auto merge of #95257 - compiler-errors:fn-borrow, r=lcnr
Add suggestion to borrow `Fn` and `FnMut` params/opaque/closures instead of move I think that Closure/ParamTy/Opaque are all "opaque" enough that it's meaningful to suggest borrowing them instead of moving them at their usage sites when we see a move error. See the attached issue for example. Is this suggestion too general? I could perhaps use the move site information to limit this to places like fn calls, but I don't know enough about mir borrowck to know if that's an easy change. Fixes #90828
2 parents 14328e6 + ac95e80 commit abf0ec8

10 files changed

+271
-72
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+155-69
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};
@@ -151,6 +151,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
151151
.args_or_use()
152152
})
153153
.collect::<Vec<Span>>();
154+
154155
let reinits = maybe_reinitialized_locations.len();
155156
if reinits == 1 {
156157
err.span_label(reinit_spans[0], "this reinitialization might get skipped");
@@ -276,76 +277,23 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
276277
}
277278
}
278279

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-
};
286-
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());
290-
if let Some(hir_generics) = tcx
291-
.typeck_root_def_id(self.mir_def_id().to_def_id())
292-
.as_local()
293-
.and_then(|def_id| tcx.hir().get_generics(def_id))
294-
{
295-
let predicates: Result<Vec<_>, _> = tcx.infer_ctxt().enter(|infcx| {
296-
let mut fulfill_cx =
297-
<dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx);
298-
299-
let copy_did = infcx.tcx.lang_items().copy_trait().unwrap();
300-
let cause = ObligationCause::new(
301-
span,
302-
self.mir_hir_id(),
303-
rustc_infer::traits::ObligationCauseCode::MiscObligation,
304-
);
305-
fulfill_cx.register_bound(
306-
&infcx,
307-
self.param_env,
308-
// Erase any region vids from the type, which may not be resolved
309-
infcx.tcx.erase_regions(ty),
310-
copy_did,
311-
cause,
312-
);
313-
// Select all, including ambiguous predicates
314-
let errors = fulfill_cx.select_all_or_error(&infcx);
315-
316-
// Only emit suggestion if all required predicates are on generic
317-
errors
318-
.into_iter()
319-
.map(|err| match err.obligation.predicate.kind().skip_binder() {
320-
PredicateKind::Trait(predicate) => {
321-
match predicate.self_ty().kind() {
322-
ty::Param(param_ty) => Ok((
323-
generics.type_param(param_ty, tcx),
324-
predicate.trait_ref.print_only_trait_path().to_string(),
325-
)),
326-
_ => Err(()),
327-
}
328-
}
329-
_ => Err(()),
330-
})
331-
.collect()
332-
});
333-
334-
if let Ok(predicates) = predicates {
335-
suggest_constraining_type_params(
336-
tcx,
337-
hir_generics,
338-
&mut err,
339-
predicates.iter().map(|(param, constraint)| {
340-
(param.name.as_str(), &**constraint, None)
341-
}),
342-
);
343-
}
344-
}
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+
};
286+
if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg) {
287+
// Suppress the next suggestion since we don't want to put more bounds onto
288+
// something that already has `Fn`-like bounds (or is a closure), so we can't
289+
// restrict anyways.
290+
} else {
291+
self.suggest_adding_copy_bounds(&mut err, ty, span);
292+
}
345293

294+
if needs_note {
346295
let span = if let Some(local) = place.as_local() {
347-
let decl = &self.body.local_decls[local];
348-
Some(decl.source_info.span)
296+
Some(self.body.local_decls[local].source_info.span)
349297
} else {
350298
None
351299
};
@@ -373,6 +321,144 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
373321
}
374322
}
375323

324+
fn suggest_borrow_fn_like(
325+
&self,
326+
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
327+
ty: Ty<'tcx>,
328+
move_sites: &[MoveSite],
329+
value_name: &str,
330+
) -> bool {
331+
let tcx = self.infcx.tcx;
332+
333+
// Find out if the predicates show that the type is a Fn or FnMut
334+
let find_fn_kind_from_did = |predicates: &[(ty::Predicate<'tcx>, Span)], substs| {
335+
predicates.iter().find_map(|(pred, _)| {
336+
let pred = if let Some(substs) = substs {
337+
pred.subst(tcx, substs).kind().skip_binder()
338+
} else {
339+
pred.kind().skip_binder()
340+
};
341+
if let ty::PredicateKind::Trait(pred) = pred && pred.self_ty() == ty {
342+
if Some(pred.def_id()) == tcx.lang_items().fn_trait() {
343+
return Some(hir::Mutability::Not);
344+
} else if Some(pred.def_id()) == tcx.lang_items().fn_mut_trait() {
345+
return Some(hir::Mutability::Mut);
346+
}
347+
}
348+
None
349+
})
350+
};
351+
352+
// If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
353+
// borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
354+
// These types seem reasonably opaque enough that they could be substituted with their
355+
// borrowed variants in a function body when we see a move error.
356+
let borrow_level = match ty.kind() {
357+
ty::Param(_) => find_fn_kind_from_did(
358+
tcx.explicit_predicates_of(self.mir_def_id().to_def_id()).predicates,
359+
None,
360+
),
361+
ty::Opaque(did, substs) => {
362+
find_fn_kind_from_did(tcx.explicit_item_bounds(*did), Some(*substs))
363+
}
364+
ty::Closure(_, substs) => match substs.as_closure().kind() {
365+
ty::ClosureKind::Fn => Some(hir::Mutability::Not),
366+
ty::ClosureKind::FnMut => Some(hir::Mutability::Mut),
367+
_ => None,
368+
},
369+
_ => None,
370+
};
371+
372+
let Some(borrow_level) = borrow_level else { return false; };
373+
let sugg = move_sites
374+
.iter()
375+
.map(|move_site| {
376+
let move_out = self.move_data.moves[(*move_site).moi];
377+
let moved_place = &self.move_data.move_paths[move_out.path].place;
378+
let move_spans = self.move_spans(moved_place.as_ref(), move_out.source);
379+
let move_span = move_spans.args_or_use();
380+
let suggestion = if borrow_level == hir::Mutability::Mut {
381+
"&mut ".to_string()
382+
} else {
383+
"&".to_string()
384+
};
385+
(move_span.shrink_to_lo(), suggestion)
386+
})
387+
.collect();
388+
err.multipart_suggestion_verbose(
389+
&format!(
390+
"consider {}borrowing {value_name}",
391+
if borrow_level == hir::Mutability::Mut { "mutably " } else { "" }
392+
),
393+
sugg,
394+
Applicability::MaybeIncorrect,
395+
);
396+
true
397+
}
398+
399+
fn suggest_adding_copy_bounds(
400+
&self,
401+
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
402+
ty: Ty<'tcx>,
403+
span: Span,
404+
) {
405+
let tcx = self.infcx.tcx;
406+
let generics = tcx.generics_of(self.mir_def_id());
407+
408+
let Some(hir_generics) = tcx
409+
.typeck_root_def_id(self.mir_def_id().to_def_id())
410+
.as_local()
411+
.and_then(|def_id| tcx.hir().get_generics(def_id))
412+
else { return; };
413+
// Try to find predicates on *generic params* that would allow copying `ty`
414+
let predicates: Result<Vec<_>, _> = tcx.infer_ctxt().enter(|infcx| {
415+
let mut fulfill_cx = <dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx);
416+
417+
let copy_did = infcx.tcx.lang_items().copy_trait().unwrap();
418+
let cause = ObligationCause::new(
419+
span,
420+
self.mir_hir_id(),
421+
rustc_infer::traits::ObligationCauseCode::MiscObligation,
422+
);
423+
fulfill_cx.register_bound(
424+
&infcx,
425+
self.param_env,
426+
// Erase any region vids from the type, which may not be resolved
427+
infcx.tcx.erase_regions(ty),
428+
copy_did,
429+
cause,
430+
);
431+
// Select all, including ambiguous predicates
432+
let errors = fulfill_cx.select_all_or_error(&infcx);
433+
434+
// Only emit suggestion if all required predicates are on generic
435+
errors
436+
.into_iter()
437+
.map(|err| match err.obligation.predicate.kind().skip_binder() {
438+
PredicateKind::Trait(predicate) => match predicate.self_ty().kind() {
439+
ty::Param(param_ty) => Ok((
440+
generics.type_param(param_ty, tcx),
441+
predicate.trait_ref.print_only_trait_path().to_string(),
442+
)),
443+
_ => Err(()),
444+
},
445+
_ => Err(()),
446+
})
447+
.collect()
448+
});
449+
450+
if let Ok(predicates) = predicates {
451+
suggest_constraining_type_params(
452+
tcx,
453+
hir_generics,
454+
err,
455+
predicates
456+
.iter()
457+
.map(|(param, constraint)| (param.name.as_str(), &**constraint, None)),
458+
);
459+
}
460+
}
461+
376462
pub(crate) fn report_move_out_while_borrowed(
377463
&mut self,
378464
location: Location,

src/test/ui/chalkify/closure.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
1212
|
1313
LL | a = 1;
1414
| ^
15+
help: consider mutably borrowing `b`
16+
|
17+
LL | let mut c = &mut b;
18+
| ++++
1519

1620
error: aborting due to previous error
1721

src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-multi-variant-diagnostics.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
1111
|
1212
LL | if let MultiVariant::Point(ref mut x, _) = point {
1313
| ^^^^^
14+
help: consider mutably borrowing `c`
15+
|
16+
LL | let a = &mut c;
17+
| ++++
1418

1519
error: aborting due to previous error
1620

src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-single-variant-diagnostics.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
1111
|
1212
LL | let SingleVariant::Point(ref mut x, _) = point;
1313
| ^^^^^
14+
help: consider mutably borrowing `c`
15+
|
16+
LL | let b = &mut c;
17+
| ++++
1418

1519
error: aborting due to previous error
1620

src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-struct-diagnostics.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
1111
|
1212
LL | x.y.a += 1;
1313
| ^^^^^
14+
help: consider mutably borrowing `hello`
15+
|
16+
LL | let b = &mut hello;
17+
| ++++
1418

1519
error: aborting due to previous error
1620

src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-tuple-diagnostics-1.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
1111
|
1212
LL | x.0 += 1;
1313
| ^^^
14+
help: consider mutably borrowing `hello`
15+
|
16+
LL | let b = &mut hello;
17+
| ++++
1418

1519
error: aborting due to previous error
1620

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 mut closure = || {
21+
x += 1;
22+
};
23+
takes_fnonce(closure);
24+
//~^ HELP consider mutably borrowing
25+
closure();
26+
//~^ ERROR borrow of moved value
27+
}
28+
29+
fn maybe() -> bool {
30+
false
31+
}
32+
33+
// Could also be Fn[Mut], here it doesn't matter
34+
fn takes_fnonce(_: impl FnOnce()) {}
35+
36+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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[E0382]: borrow of moved value: `closure`
33+
--> $DIR/borrow-closures-instead-of-move.rs:25:5
34+
|
35+
LL | takes_fnonce(closure);
36+
| ------- value moved here
37+
LL |
38+
LL | closure();
39+
| ^^^^^^^ value borrowed here after move
40+
|
41+
note: closure cannot be moved more than once as it is not `Copy` due to moving the variable `x` out of its environment
42+
--> $DIR/borrow-closures-instead-of-move.rs:21:9
43+
|
44+
LL | x += 1;
45+
| ^
46+
help: consider mutably borrowing `closure`
47+
|
48+
LL | takes_fnonce(&mut closure);
49+
| ++++
50+
51+
error: aborting due to 3 previous errors
52+
53+
For more information about this error, try `rustc --explain E0382`.

0 commit comments

Comments
 (0)