diff options
| author | Michael Goulet <michael@errs.io> | 2022-03-23 16:15:43 -0700 |
|---|---|---|
| committer | Michael Goulet <michael@errs.io> | 2022-03-28 22:27:06 -0700 |
| commit | 0f3c2933e05321f214616ae084d86f4081866f44 (patch) | |
| tree | 3d0d1b1b9460c0991ad8dbe4f2bcfd606efbf9c3 | |
| parent | ad5b6f6b72091261ac421d1331b2350f628d1fcb (diff) | |
Add suggestion to borrow opaque Fn and FnMut instead of move
4 files changed, 161 insertions, 16 deletions
diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 66a23eb4125..8bf6ee57997 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -12,7 +12,7 @@ use rustc_middle::mir::{ FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, VarBindingForm, }; -use rustc_middle::ty::{self, suggest_constraining_type_params, PredicateKind, Ty}; +use rustc_middle::ty::{self, subst::Subst, suggest_constraining_type_params, PredicateKind, Ty}; use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex}; use rustc_span::symbol::sym; use rustc_span::{BytePos, MultiSpan, Span}; @@ -276,22 +276,25 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } - if needs_note { - let opt_name = - self.describe_place_with_options(place.as_ref(), IncludingDowncast(true)); - let note_msg = match opt_name { - Some(ref name) => format!("`{}`", name), - None => "value".to_owned(), - }; + let opt_name = + self.describe_place_with_options(place.as_ref(), IncludingDowncast(true)); + let note_msg = match opt_name { + Some(ref name) => format!("`{}`", name), + None => "value".to_owned(), + }; + + let tcx = self.infcx.tcx; + let generics = tcx.generics_of(self.mir_def_id()); - // Try to find predicates on *generic params* that would allow copying `ty` - let tcx = self.infcx.tcx; - let generics = tcx.generics_of(self.mir_def_id()); + if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, ¬e_msg) { + // Suppress the next note, since we don't want to put more `Fn`-like bounds onto something that already has them + } else if needs_note { if let Some(hir_generics) = tcx .typeck_root_def_id(self.mir_def_id().to_def_id()) .as_local() .and_then(|def_id| tcx.hir().get_generics(def_id)) { + // Try to find predicates on *generic params* that would allow copying `ty` let predicates: Result<Vec<_>, _> = tcx.infer_ctxt().enter(|infcx| { let mut fulfill_cx = <dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx); @@ -344,8 +347,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } let span = if let Some(local) = place.as_local() { - let decl = &self.body.local_decls[local]; - Some(decl.source_info.span) + Some(self.body.local_decls[local].source_info.span) } else { None }; @@ -373,6 +375,81 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } + fn suggest_borrow_fn_like( + &self, + err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + ty: Ty<'tcx>, + move_sites: &[MoveSite], + value_name: &str, + ) -> bool { + let tcx = self.infcx.tcx; + + // Find out if the predicates show that the type is a Fn or FnMut + let find_fn_kind_from_did = |predicates: &[(ty::Predicate<'tcx>, Span)], substs| { + predicates.iter().find_map(|(pred, _)| { + let pred = if let Some(substs) = substs { + pred.subst(tcx, substs).kind().skip_binder() + } else { + pred.kind().skip_binder() + }; + if let ty::PredicateKind::Trait(pred) = pred && pred.self_ty() == ty { + if Some(pred.def_id()) == tcx.lang_items().fn_trait() { + return Some(hir::Mutability::Not); + } else if Some(pred.def_id()) == tcx.lang_items().fn_mut_trait() { + return Some(hir::Mutability::Mut); + } + } + None + }) + }; + + // If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably) + // borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`. + // These types seem reasonably opaque enough that they could be substituted with their + // borrowed variants in a function body when we see a move error. + let borrow_level = match ty.kind() { + ty::Param(_) => find_fn_kind_from_did( + tcx.explicit_predicates_of(self.mir_def_id().to_def_id()).predicates, + None, + ), + ty::Opaque(did, substs) => { + find_fn_kind_from_did(tcx.explicit_item_bounds(*did), Some(*substs)) + } + ty::Closure(_, substs) => match substs.as_closure().kind() { + ty::ClosureKind::Fn => Some(hir::Mutability::Not), + ty::ClosureKind::FnMut => Some(hir::Mutability::Mut), + _ => None, + }, + _ => None, + }; + + let Some(borrow_level) = borrow_level else { return false; }; + let sugg = move_sites + .iter() + .map(|move_site| { + let move_out = self.move_data.moves[(*move_site).moi]; + let moved_place = &self.move_data.move_paths[move_out.path].place; + let move_spans = self.move_spans(moved_place.as_ref(), move_out.source); + let move_span = move_spans.args_or_use(); + let suggestion = if borrow_level == hir::Mutability::Mut { + "&mut ".to_string() + } else { + "&".to_string() + }; + (move_span.shrink_to_lo(), suggestion) + }) + .collect(); + err.multipart_suggestion_verbose( + &format!( + "consider {}borrowing {value_name}", + if borrow_level == hir::Mutability::Mut { "mutably " } else { "" } + ), + sugg, + Applicability::MaybeIncorrect, + ); + true + } + pub(crate) fn report_move_out_while_borrowed( &mut self, location: Location, diff --git a/src/test/ui/moves/borrow-closures-instead-of-move.rs b/src/test/ui/moves/borrow-closures-instead-of-move.rs new file mode 100644 index 00000000000..adbbf4ee1a8 --- /dev/null +++ b/src/test/ui/moves/borrow-closures-instead-of-move.rs @@ -0,0 +1,34 @@ +fn takes_fn(f: impl Fn()) { + loop { + takes_fnonce(f); + //~^ ERROR use of moved value + //~| HELP consider borrowing + } +} + +fn takes_fn_mut(m: impl FnMut()) { + if maybe() { + takes_fnonce(m); + //~^ HELP consider mutably borrowing + } + takes_fnonce(m); + //~^ ERROR use of moved value +} + +fn has_closure() { + let mut x = 0; + let closure = || { + x += 1; + }; + takes_fnonce(closure); + closure(); +} + +fn maybe() -> bool { + false +} + +// Could also be Fn[Mut], here it doesn't matter +fn takes_fnonce(_: impl FnOnce()) {} + +fn main() {} diff --git a/src/test/ui/moves/borrow-closures-instead-of-move.stderr b/src/test/ui/moves/borrow-closures-instead-of-move.stderr new file mode 100644 index 00000000000..ff10d3c6006 --- /dev/null +++ b/src/test/ui/moves/borrow-closures-instead-of-move.stderr @@ -0,0 +1,34 @@ +error[E0382]: use of moved value: `f` + --> $DIR/borrow-closures-instead-of-move.rs:3:22 + | +LL | fn takes_fn(f: impl Fn()) { + | - move occurs because `f` has type `impl Fn()`, which does not implement the `Copy` trait +LL | loop { +LL | takes_fnonce(f); + | ^ value moved here, in previous iteration of loop + | +help: consider borrowing `f` + | +LL | takes_fnonce(&f); + | + + +error[E0382]: use of moved value: `m` + --> $DIR/borrow-closures-instead-of-move.rs:14:18 + | +LL | fn takes_fn_mut(m: impl FnMut()) { + | - move occurs because `m` has type `impl FnMut()`, which does not implement the `Copy` trait +LL | if maybe() { +LL | takes_fnonce(m); + | - value moved here +... +LL | takes_fnonce(m); + | ^ value used here after move + | +help: consider mutably borrowing `m` + | +LL | takes_fnonce(&mut m); + | ++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0382`. diff --git a/src/test/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr b/src/test/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr index 2adaf576adf..4759b45892c 100644 --- a/src/test/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr +++ b/src/test/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr @@ -17,10 +17,10 @@ LL | let mut r = R {c: Box::new(f)}; LL | f(&mut r, false) | ^ value borrowed here after move | -help: consider further restricting this bound +help: consider mutably borrowing `f` | -LL | fn conspirator<F>(mut f: F) where F: FnMut(&mut R, bool) + Copy { - | ++++++ +LL | let mut r = R {c: Box::new(&mut f)}; + | ++++ error: aborting due to 2 previous errors |
