diff options
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs | 103 |
1 files changed, 90 insertions, 13 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, |
